Skip to content

Commit

Permalink
🚜 Move client to mcp-server command
Browse files Browse the repository at this point in the history
  • Loading branch information
wesen committed Feb 10, 2025
1 parent 17d8b50 commit 60c3fd1
Show file tree
Hide file tree
Showing 8 changed files with 801 additions and 3 deletions.
22 changes: 22 additions & 0 deletions cmd/mcp-server/cmds/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cmds

import (
"github.com/go-go-golems/glazed/pkg/help"
"github.com/go-go-golems/go-go-mcp/cmd/mcp-server/cmds/client"
"github.com/spf13/cobra"
)

var ClientCmd = &cobra.Command{
Use: "client",
Short: "MCP client functionality",
Long: `Client commands for interacting with MCP servers`,
}

func InitClientCommand(helpSystem *help.HelpSystem) error {
// Add client subcommands
ClientCmd.AddCommand(client.ToolsCmd)
ClientCmd.AddCommand(client.ResourcesCmd)
ClientCmd.AddCommand(client.PromptsCmd)

return nil
}
85 changes: 85 additions & 0 deletions cmd/mcp-server/cmds/client/helpers/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package helpers

import (
"context"
"fmt"

glazed_layers "github.com/go-go-golems/glazed/pkg/cmds/layers"
"github.com/go-go-golems/go-go-mcp/cmd/mcp-client/cmds/layers"
"github.com/go-go-golems/go-go-mcp/pkg/client"
"github.com/go-go-golems/go-go-mcp/pkg/protocol"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)

type ClientSettings = layers.ClientSettings

// CreateClient initializes and returns a new MCP client based on the provided flags.
func CreateClient(cmd *cobra.Command) (*client.Client, error) {
transport, err := cmd.Flags().GetString("transport")
if err != nil {
return nil, fmt.Errorf("failed to get transport flag: %w", err)
}

serverURL, err := cmd.Flags().GetString("server")
if err != nil {
return nil, fmt.Errorf("failed to get server flag: %w", err)
}

cmdArgs, err := cmd.Flags().GetStringSlice("command")
if err != nil {
return nil, fmt.Errorf("failed to get command flag: %w", err)
}

return createClient(&ClientSettings{
Transport: transport,
Server: serverURL,
Command: cmdArgs,
})
}

// CreateClientFromSettings initializes and returns a new MCP client based on the provided settings.
func CreateClientFromSettings(parsedLayers *glazed_layers.ParsedLayers) (*client.Client, error) {
s := &ClientSettings{}
if err := parsedLayers.InitializeStruct(layers.ClientLayerSlug, s); err != nil {
return nil, err
}

return createClient(s)
}

func createClient(s *ClientSettings) (*client.Client, error) {
var t client.Transport
var err error

switch s.Transport {
case "command":
if len(s.Command) == 0 {
return nil, fmt.Errorf("command is required for command transport")
}
log.Debug().Msgf("Creating command transport with args: %v", s.Command)
t, err = client.NewCommandStdioTransport(log.Logger, s.Command[0], s.Command[1:]...)
if err != nil {
return nil, fmt.Errorf("failed to create command transport: %w", err)
}
case "sse":
log.Debug().Msgf("Creating SSE transport with server URL: %s", s.Server)
t = client.NewSSETransport(s.Server, log.Logger)
default:
return nil, fmt.Errorf("invalid transport type: %s", s.Transport)
}

// Create and initialize client
c := client.NewClient(log.Logger, t)
log.Debug().Msgf("Initializing client")
err = c.Initialize(context.Background(), protocol.ClientCapabilities{
Sampling: &protocol.SamplingCapability{},
})
if err != nil {
return nil, fmt.Errorf("failed to initialize client: %w", err)
}

log.Debug().Msgf("Client initialized")

return c, nil
}
39 changes: 39 additions & 0 deletions cmd/mcp-server/cmds/client/layers/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package layers

import (
"github.com/go-go-golems/glazed/pkg/cmds/layers"
"github.com/go-go-golems/glazed/pkg/cmds/parameters"
)

type ClientSettings struct {
Transport string `glazed.parameter:"transport"`
Server string `glazed.parameter:"server"`
Command []string `glazed.parameter:"command"`
}

const ClientLayerSlug = "mcp-client"

func NewClientParameterLayer() (layers.ParameterLayer, error) {
return layers.NewParameterLayer(ClientLayerSlug, "MCP Client Settings",
layers.WithParameterDefinitions(
parameters.NewParameterDefinition(
"transport",
parameters.ParameterTypeString,
parameters.WithHelp("Transport type (command or sse)"),
parameters.WithDefault("command"),
),
parameters.NewParameterDefinition(
"server",
parameters.ParameterTypeString,
parameters.WithHelp("Server URL for SSE transport"),
parameters.WithDefault("http://localhost:3001"),
),
parameters.NewParameterDefinition(
"command",
parameters.ParameterTypeStringList,
parameters.WithHelp("Command and arguments for command transport"),
parameters.WithDefault([]string{"mcp-server", "start", "--transport", "stdio"}),
),
),
)
}
203 changes: 203 additions & 0 deletions cmd/mcp-server/cmds/client/prompts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package client

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

"github.com/go-go-golems/glazed/pkg/cli"
"github.com/go-go-golems/glazed/pkg/cmds"
glazed_layers "github.com/go-go-golems/glazed/pkg/cmds/layers"
"github.com/go-go-golems/go-go-mcp/cmd/mcp-client/cmds/layers"

"github.com/go-go-golems/glazed/pkg/cmds/parameters"
"github.com/go-go-golems/glazed/pkg/middlewares"
"github.com/go-go-golems/glazed/pkg/settings"
"github.com/go-go-golems/glazed/pkg/types"
"github.com/go-go-golems/go-go-mcp/cmd/mcp-client/cmds/helpers"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

var (
promptArgs string
)

// PromptsCmd handles the "prompts" command group
var PromptsCmd = &cobra.Command{
Use: "prompts",
Short: "Interact with prompts",
Long: `List available prompts and execute specific prompts.`,
}

type ListPromptsCommand struct {
*cmds.CommandDescription
}

type ExecutePromptCommand struct {
*cmds.CommandDescription
}

type ExecutePromptSettings struct {
Args string `glazed.parameter:"args"`
PromptName string `glazed.parameter:"prompt-name"`
}

func NewListPromptsCommand() (*ListPromptsCommand, error) {
glazedParameterLayer, err := settings.NewGlazedParameterLayers()
if err != nil {
return nil, errors.Wrap(err, "could not create Glazed parameter layer")
}

clientLayer, err := layers.NewClientParameterLayer()
if err != nil {
return nil, errors.Wrap(err, "could not create client parameter layer")
}

return &ListPromptsCommand{
CommandDescription: cmds.NewCommandDescription(
"list",
cmds.WithShort("List available prompts"),
cmds.WithLayersList(
glazedParameterLayer,
clientLayer,
),
),
}, nil
}

func NewExecutePromptCommand() (*ExecutePromptCommand, error) {
clientLayer, err := layers.NewClientParameterLayer()
if err != nil {
return nil, errors.Wrap(err, "could not create client parameter layer")
}

return &ExecutePromptCommand{
CommandDescription: cmds.NewCommandDescription(
"execute",
cmds.WithShort("Execute a specific prompt"),
cmds.WithFlags(
parameters.NewParameterDefinition(
"args",
parameters.ParameterTypeString,
parameters.WithHelp("Prompt arguments as JSON string"),
parameters.WithDefault(""),
),
),
cmds.WithArguments(
parameters.NewParameterDefinition(
"prompt-name",
parameters.ParameterTypeString,
parameters.WithHelp("Name of the prompt to execute"),
parameters.WithRequired(true),
),
),
cmds.WithLayersList(
clientLayer,
),
),
}, nil
}

func (c *ListPromptsCommand) RunIntoGlazeProcessor(
ctx context.Context,
parsedLayers *glazed_layers.ParsedLayers,
gp middlewares.Processor,
) error {
client, err := helpers.CreateClientFromSettings(parsedLayers)
if err != nil {
return err
}
defer client.Close(ctx)

prompts, cursor, err := client.ListPrompts(ctx, "")
if err != nil {
return err
}

for _, prompt := range prompts {
row := types.NewRow(
types.MRP("name", prompt.Name),
types.MRP("description", prompt.Description),
)

// Create a JSON array of arguments
args := make([]map[string]interface{}, len(prompt.Arguments))
for i, arg := range prompt.Arguments {
args[i] = map[string]interface{}{
"name": arg.Name,
"required": arg.Required,
"description": arg.Description,
}
}
row.Set("arguments", args)

if err := gp.AddRow(ctx, row); err != nil {
return err
}
}

if cursor != "" {
// Add cursor as a final row
cursorRow := types.NewRow(
types.MRP("cursor", cursor),
)
if err := gp.AddRow(ctx, cursorRow); err != nil {
return err
}
}

return nil
}

func (c *ExecutePromptCommand) RunIntoWriter(
ctx context.Context,
parsedLayers *glazed_layers.ParsedLayers,
w io.Writer,
) error {
s := &ExecutePromptSettings{}
if err := parsedLayers.InitializeStruct(glazed_layers.DefaultSlug, s); err != nil {
return err
}

client, err := helpers.CreateClientFromSettings(parsedLayers)
if err != nil {
return err
}
defer client.Close(ctx)

// Parse prompt arguments
promptArgMap := make(map[string]string)
if s.Args != "" {
if err := json.Unmarshal([]byte(s.Args), &promptArgMap); err != nil {
return fmt.Errorf("invalid prompt arguments JSON: %w", err)
}
}

message, err := client.GetPrompt(ctx, s.PromptName, promptArgMap)
if err != nil {
return err
}

// Write formatted output to writer
_, err = fmt.Fprintf(w, "Role: %s\nContent: %s\n", message.Role, message.Content.Text)
return err
}

func init() {
listCmd, err := NewListPromptsCommand()
cobra.CheckErr(err)

executeCmd, err := NewExecutePromptCommand()
cobra.CheckErr(err)

listCobraCmd, err := cli.BuildCobraCommandFromGlazeCommand(listCmd)
cobra.CheckErr(err)

executeCobraCmd, err := cli.BuildCobraCommandFromWriterCommand(executeCmd)
cobra.CheckErr(err)

PromptsCmd.AddCommand(listCobraCmd)
PromptsCmd.AddCommand(executeCobraCmd)
}
Loading

0 comments on commit 60c3fd1

Please sign in to comment.