Skip to content
Merged
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
41 changes: 32 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,16 @@ mcp tools npx -y @modelcontextprotocol/server-filesystem ~
The default format now displays tools in a colorized man-page style:

```
read_file(path:str, [limit:int], [offset:int])
Reads a file from the filesystem

read_file(path:str)
Read the complete contents of a file from the file system.
read_multiple_files(paths:str[])
Read the contents of multiple files simultaneously.
list_dir(path:str)
Lists directory contents

Lists the contents of a directory.
write_file(path:str, content:str)
Writes content to a file.
grep_search(pattern:str, [excludePatterns:str[]])
Search files with pattern

Search files with pattern.
edit_file(edits:{newText:str,oldText:str}[], path:str)
Edit a file with multiple text replacements
```
Expand Down Expand Up @@ -236,7 +237,7 @@ mcp call read_file --params '{"path":"/path/to/file"}' npx -y @modelcontextproto
mcp call resource:test://static/resource/1 npx -y @modelcontextprotocol/server-everything -f json | jq ".contents[0].text"
```

or
or

```bash
mcp read-resource test://static/resource/1 npx -y @modelcontextprotocol/server-everything -f json | jq ".contents[0].text"
Expand All @@ -248,6 +249,28 @@ mcp read-resource test://static/resource/1 npx -y @modelcontextprotocol/server-e
mcp get-prompt simple_prompt npx -y @modelcontextprotocol/server-everything -f json | jq ".messages[0].content.text"
```

#### Viewing Server Logs

When using client commands that make calls to the server, you can add the `--server-logs` flag to see the server logs related to your request:

```bash
# View server logs when listing tools
mcp tools --server-logs npx -y @modelcontextprotocol/server-filesystem ~
```

Output:
```
[>] Secure MCP Filesystem Server running on stdio
[>] Allowed directories: [ '/Users/fka/' ]
read_file(path:str)
Read the complete contents of a file from the file system.
read_multiple_files(paths:str[])
Read the contents of multiple files simultaneously.
... and the other tools available on this server
```

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

### Interactive Shell

The interactive shell mode allows you to run multiple MCP commands in a single session:
Expand Down Expand Up @@ -330,7 +353,7 @@ After scaffolding, you can build and run your MCP server:
npm install

# Build the TypeScript code
npm run build
npm run build

# Test the server with MCP Tools
mcp tools node build/index.js
Expand Down
3 changes: 3 additions & 0 deletions cmd/mcptools/commands/get_prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ func GetPromptCmd() *cobra.Command {
case (cmdArgs[i] == FlagParams || cmdArgs[i] == FlagParamsShort) && i+1 < len(cmdArgs):
ParamsString = cmdArgs[i+1]
i += 2
case cmdArgs[i] == FlagServerLogs:
ShowServerLogs = true
i++
case !promptExtracted:
promptName = cmdArgs[i]
promptExtracted = true
Expand Down
4 changes: 2 additions & 2 deletions cmd/mcptools/commands/read_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func ReadResourceCmd() *cobra.Command {
fmt.Fprintln(os.Stderr, "Error: resource name is required")
fmt.Fprintln(
os.Stderr,
"Example: mcp read-resource npx -y @modelcontextprotocol/server-filesystem ~",
"Example: mcp read-resource test://static/resource/1 npx -y @modelcontextprotocol/server-filesystem ~",
)
os.Exit(1)
}
Expand Down Expand Up @@ -58,7 +58,7 @@ func ReadResourceCmd() *cobra.Command {
fmt.Fprintln(os.Stderr, "Error: resource name is required")
fmt.Fprintln(
os.Stderr,
"Example: mcp read-resource npx -y @modelcontextprotocol/server-filesystem ~",
"Example: mcp read-resource test://static/resource/1 npx -y @modelcontextprotocol/server-filesystem ~",
)
os.Exit(1)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/mcptools/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
FlagParamsShort = "-p"
FlagHelp = "--help"
FlagHelpShort = "-h"
FlagServerLogs = "--server-logs"
)

// entity types.
Expand All @@ -31,6 +32,8 @@ var (
FormatOption = "table"
// ParamsString is the params for the command.
ParamsString string
// ShowServerLogs is a flag to show server logs.
ShowServerLogs bool
)

// RootCmd creates the root command.
Expand Down
8 changes: 6 additions & 2 deletions cmd/mcptools/commands/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ func ShellCmd() *cobra.Command { //nolint:gocyclo
parsedArgs := []string{}

for i := 0; i < len(cmdArgs); i++ {
if (cmdArgs[i] == FlagFormat || cmdArgs[i] == FlagFormatShort) && i+1 < len(cmdArgs) {
switch {
case (cmdArgs[i] == FlagFormat || cmdArgs[i] == FlagFormatShort) && i+1 < len(cmdArgs):
FormatOption = cmdArgs[i+1]
i++
} else {
case cmdArgs[i] == FlagServerLogs:
ShowServerLogs = true
i++
default:
parsedArgs = append(parsedArgs, cmdArgs[i])
}
}
Expand Down
5 changes: 5 additions & 0 deletions cmd/mcptools/commands/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ var CreateClientFunc = func(args []string, opts ...client.Option) (*client.Clien
return nil, ErrCommandRequired
}

opts = append(opts, client.SetShowServerLogs(ShowServerLogs))

// Check if the first argument is an alias
if len(args) == 1 {
server, found := alias.GetServerCommand(args[0])
Expand Down Expand Up @@ -64,6 +66,9 @@ func ProcessFlags(args []string) []string {
case (args[i] == FlagFormat || args[i] == FlagFormatShort) && i+1 < len(args):
FormatOption = args[i+1]
i += 2
case args[i] == FlagServerLogs:
ShowServerLogs = true
i++
default:
parsedArgs = append(parsedArgs, args[i])
i++
Expand Down
14 changes: 14 additions & 0 deletions cmd/mcptools/commands/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,56 @@ func TestProcessFlags(t *testing.T) {
args []string
wantArgs []string
wantFormat string
showLogs bool
}{
{
name: "no flags",
args: []string{"cmd", "arg1", "arg2"},
wantArgs: []string{"cmd", "arg1", "arg2"},
wantFormat: "",
showLogs: false,
},
{
name: "with long format flag",
args: []string{"cmd", "--format", "json", "arg1"},
wantArgs: []string{"cmd", "arg1"},
wantFormat: "json",
showLogs: false,
},
{
name: "with short format flag",
args: []string{"cmd", "-f", "pretty", "arg1"},
wantArgs: []string{"cmd", "arg1"},
wantFormat: "pretty",
showLogs: false,
},
{
name: "with format flag at end",
args: []string{"cmd", "arg1", "--format", "table"},
wantArgs: []string{"cmd", "arg1"},
wantFormat: "table",
showLogs: false,
},
{
name: "with invalid format option",
args: []string{"cmd", "--format", "invalid", "arg1"},
wantArgs: []string{"cmd", "arg1"},
wantFormat: "invalid",
showLogs: false,
},
{
name: "with format flag without value",
args: []string{"cmd", "--format", "json"},
wantArgs: []string{"cmd"},
wantFormat: "json",
showLogs: false,
},
{
name: "with server logs flag",
args: []string{"cmd", "--server-logs", "arg1"},
wantArgs: []string{"cmd", "arg1"},
wantFormat: "",
showLogs: true,
},
}

Expand Down
10 changes: 10 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ func CloseTransportAfterExecute(closeTransport bool) Option {
}
}

// SetShowServerLogs sets whether to show server logs.
func SetShowServerLogs(showLogs bool) Option {
return func(c *Client) {
t, ok := c.transport.(interface{ SetShowServerLogs(bool) })
if ok {
t.SetShowServerLogs(showLogs)
}
}
}

// NewWithTransport creates a new MCP client using the provided transport.
// This allows callers to provide a custom transport implementation.
func NewWithTransport(t transport.Transport) *Client {
Expand Down
43 changes: 31 additions & 12 deletions pkg/transport/stdio.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import (
"io"
"os"
"os/exec"
"strings"
"time"
)

// Stdio implements the Transport interface by executing a command
// and communicating with it via stdin/stdout using JSON-RPC.
type Stdio struct {
process *stdioProcess
command []string
nextID int
debug bool
process *stdioProcess
command []string
nextID int
debug bool
showServerLogs bool
}

// stdioProcess reflects the state of a running command.
Expand Down Expand Up @@ -50,6 +52,11 @@ func (t *Stdio) SetCloseAfterExecute(v bool) {
}
}

// SetShowServerLogs toggles whether to print server logs.
func (t *Stdio) SetShowServerLogs(v bool) {
t.showServerLogs = v
}

// Execute implements the Transport interface by spawning a subprocess
// and communicating with it via JSON-RPC over stdin/stdout.
func (t *Stdio) Execute(method string, params any) (map[string]any, error) {
Expand All @@ -72,14 +79,13 @@ func (t *Stdio) Execute(method string, params any) (map[string]any, error) {

if !process.isInitializeSent {
if initErr := t.initialize(process.stdin, process.stdout); initErr != nil {
t.printStderr(process)
if t.debug {
fmt.Fprintf(os.Stderr, "DEBUG: Initialization failed: %v\n", initErr)
if process.stderrBuf.Len() > 0 {
fmt.Fprintf(os.Stderr, "DEBUG: stderr during init: %s\n", process.stderrBuf.String())
}
}
return nil, initErr
}
t.printStderr(process)
process.isInitializeSent = true
}

Expand All @@ -100,10 +106,10 @@ func (t *Stdio) Execute(method string, params any) (map[string]any, error) {
}

response, err := t.readResponse(process.stdout)
t.printStderr(process)
if err != nil {
return nil, err
}

err = t.closeProcess(process)
if err != nil {
return nil, err
Expand All @@ -112,6 +118,22 @@ func (t *Stdio) Execute(method string, params any) (map[string]any, error) {
return response.Result, nil
}

// printStderr prints and clears any accumulated stderr output.
func (t *Stdio) printStderr(process *stdioProcess) {
if !t.showServerLogs {
return
}
if process.stderrBuf.Len() > 0 {
for _, line := range strings.SplitAfter(process.stderrBuf.String(), "\n") {
line = strings.TrimSuffix(line, "\n")
if line != "" {
fmt.Fprintf(os.Stderr, "[>] %s\n", line)
}
}
process.stderrBuf.Reset() // Clear the buffer after reading
}
}

// closeProcess waits for the command to finish, returning any error.
func (t *Stdio) closeProcess(process *stdioProcess) error {
if t.process != nil {
Expand All @@ -130,13 +152,10 @@ func (t *Stdio) closeProcess(process *stdioProcess) error {
case waitErr := <-done:
if t.debug {
fmt.Fprintf(os.Stderr, "DEBUG: Command completed with err: %v\n", waitErr)
if process.stderrBuf.Len() > 0 {
fmt.Fprintf(os.Stderr, "DEBUG: stderr output:\n%s\n", process.stderrBuf.String())
}
}

if waitErr != nil && process.stderrBuf.Len() > 0 {
return fmt.Errorf("command error: %w, stderr: %s", waitErr, process.stderrBuf.String())
return fmt.Errorf("command error: %w", waitErr)
}
case <-time.After(1 * time.Second):
if t.debug {
Expand Down