Skip to content

List servers command #60

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Available Commands:
alias Manage MCP server aliases
configs Manage MCP server configurations
new Create a new MCP project component
servers List the MCP servers installed in file $HOME/mcptools.config
help Help about any command
completion Generate the autocompletion script for the specified shell

Expand Down Expand Up @@ -213,6 +214,12 @@ mcp tools --format pretty npx -y @modelcontextprotocol/server-filesystem ~

MCP Tools includes several core commands for interacting with MCP servers:

#### List Installed Servers
Configure MCP Servers in $HOME/mcptools.config. See how to below in section `Installing MCP Servers`
```bash
mcp servers
```

#### List Available Tools

```bash
Expand Down Expand Up @@ -277,6 +284,35 @@ read_multiple_files(paths:str[])

This can be helpful for debugging or understanding what's happening on the server side when executing these commands.

### Configuring MCP Servers
You can provide configure of MCP Servers available in file $HOME/mcptools.config in the following format:
```
{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/path/to/allowed/files"
]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
}
}
}
}
```


### Interactive Shell

The interactive shell mode allows you to run multiple MCP commands in a single session:
Expand Down
56 changes: 56 additions & 0 deletions cmd/mcptools/commands/servers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package commands

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
)

// ServersConfig represents the root configuration structure
type ServersConfig struct {
MCPServers map[string]ServerConfig `json:"mcpServers"`
}

// ServersCmd creates the servers command.
func ServersCmd(configPath string, enableServers bool) *cobra.Command {
return &cobra.Command{
Use: "servers",
Short: "List the MCP servers installed in file $HOME/mcptools.config",
Run: func(cmd *cobra.Command, args []string) {
if !enableServers {
fmt.Fprintf(os.Stderr, "Servers command is not enabled, please create a server config at %s\n", configPath)
return
}

// Read the config file
configPath := filepath.Join(configPath)
data, err := os.ReadFile(configPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading config file: %v\n", err)
os.Exit(1)
}

// Parse the JSON
var config ServersConfig
if err := json.Unmarshal(data, &config); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n", err)
os.Exit(1)
}

// Print each server configuration
for name, server := range config.MCPServers {
// Print server name and command
fmt.Printf("%s: %s", name, server.Command)

// Print arguments
for _, arg := range server.Args {
fmt.Printf(" %s", arg)
}
fmt.Println()
}
},
}
}
131 changes: 131 additions & 0 deletions cmd/mcptools/commands/servers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package commands

import (
"encoding/json"
"io"
"os"
"path/filepath"
"testing"
)

func TestServersCmd(t *testing.T) {
// Create a temporary directory for test files
tempDir, err := os.MkdirTemp("", "mcptools-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)

// Create test config file
testConfig := ServersConfig{
MCPServers: map[string]ServerConfig{
"test-server": {
Command: "test-command",
Args: []string{"arg1", "arg2"},
},
"another-server": {
Command: "another-command",
Args: []string{"arg3"},
},
},
}

configPath := filepath.Join(tempDir, "mcp_servers.json")
configData, err := json.Marshal(testConfig)
if err != nil {
t.Fatalf("Failed to marshal test config: %v", err)
}

if err := os.WriteFile(configPath, configData, 0644); err != nil {
t.Fatalf("Failed to write test config: %v", err)
}

tests := []struct {
name string
configPath string
enableServers bool
expectedOutput string
expectError bool
}{
{
name: "successful server list",
configPath: configPath,
enableServers: true,
expectedOutput: `another-server: another-command arg3
test-server: test-command arg1 arg2
`,
expectError: false,
},
{
name: "servers disabled",
configPath: configPath,
enableServers: false,
expectedOutput: `Servers command is not enabled, please create a server config at ` + configPath + "\n",
expectError: false,
},
{
name: "non-existent config file",
configPath: filepath.Join(tempDir, "nonexistent.json"),
enableServers: true,
expectError: true,
},
{
name: "invalid json config",
configPath: filepath.Join(tempDir, "invalid.json"),
enableServers: true,
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create invalid JSON file for the invalid json test
if tt.name == "invalid json config" {
if err := os.WriteFile(tt.configPath, []byte("invalid json"), 0644); err != nil {
t.Fatalf("Failed to write invalid config: %v", err)
}
}

// Create pipes for capturing output
oldStdout := os.Stdout
oldStderr := os.Stderr
r, w, _ := os.Pipe()
os.Stdout = w
os.Stderr = w
defer func() {
os.Stdout = oldStdout
os.Stderr = oldStderr
}()

// Create a command with the test configuration
cmd := ServersCmd(tt.configPath, tt.enableServers)

// Execute the command
err := cmd.Execute()

// Close the write end of the pipe
w.Close()

// Read the output
output, _ := io.ReadAll(r)

// Check error conditions
if tt.expectError {
if err == nil {
t.Error("Expected error but got none")
}
return
}

if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}

// Check output
if got := string(output); got != tt.expectedOutput {
t.Errorf("Expected output:\n%s\nGot:\n%s", tt.expectedOutput, got)
}
})
}
}
13 changes: 13 additions & 0 deletions cmd/mcptools/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Package main implements mcp functionality.
package main

import (
"fmt"
"os"
"path/filepath"

"github.com/f/mcptools/cmd/mcptools/commands"
"github.com/spf13/cobra"
Expand All @@ -24,6 +26,16 @@ func init() {
func main() {
cobra.EnableCommandSorting = false

// check if mcptools.config exists
configPath := filepath.Join(os.Getenv("HOME"), "mcptools.config")
enableServers := false
if _, err := os.Stat(configPath); os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "No server config found at %s, servers command will not be available.\n", configPath)
} else {
fmt.Fprintf(os.Stderr, "Loading server config from %s\n", configPath)
enableServers = true
}

rootCmd := commands.RootCmd()
rootCmd.AddCommand(
commands.VersionCmd(),
Expand All @@ -41,6 +53,7 @@ func main() {
commands.ConfigsCmd(),
commands.NewCmd(),
commands.GuardCmd(),
commands.ServersCmd(configPath, enableServers),
)

if err := rootCmd.Execute(); err != nil {
Expand Down
Loading