Skip to content

Commit be1b054

Browse files
committed
better table
1 parent 6c4e3a6 commit be1b054

File tree

5 files changed

+268
-30
lines changed

5 files changed

+268
-30
lines changed

cmd/mcptools/main.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func newToolsCmd() *cobra.Command {
152152
mcpClient, err := createClient(parsedArgs)
153153
if err != nil {
154154
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
155-
fmt.Fprintf(os.Stderr, "Example: mcp tools npx -y @modelcontextprotocol/server-filesystem ~/Code\n")
155+
fmt.Fprintf(os.Stderr, "Example: mcp tools npx -y @modelcontextprotocol/server-filesystem ~\n")
156156
os.Exit(1)
157157
}
158158

@@ -182,7 +182,7 @@ func newResourcesCmd() *cobra.Command {
182182
mcpClient, err := createClient(parsedArgs)
183183
if err != nil {
184184
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
185-
fmt.Fprintf(os.Stderr, "Example: mcp resources npx -y @modelcontextprotocol/server-filesystem ~/Code\n")
185+
fmt.Fprintf(os.Stderr, "Example: mcp resources npx -y @modelcontextprotocol/server-filesystem ~\n")
186186
os.Exit(1)
187187
}
188188

@@ -212,7 +212,7 @@ func newPromptsCmd() *cobra.Command {
212212
mcpClient, err := createClient(parsedArgs)
213213
if err != nil {
214214
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
215-
fmt.Fprintf(os.Stderr, "Example: mcp prompts npx -y @modelcontextprotocol/server-filesystem ~/Code\n")
215+
fmt.Fprintf(os.Stderr, "Example: mcp prompts npx -y @modelcontextprotocol/server-filesystem ~\n")
216216
os.Exit(1)
217217
}
218218

@@ -241,7 +241,7 @@ func newCallCmd() *cobra.Command {
241241
fmt.Fprintln(os.Stderr, "Error: entity name is required")
242242
fmt.Fprintln(
243243
os.Stderr,
244-
"Example: mcp call read_file npx -y @modelcontextprotocol/server-filesystem ~/Code",
244+
"Example: mcp call read_file npx -y @modelcontextprotocol/server-filesystem ~",
245245
)
246246
os.Exit(1)
247247
}
@@ -275,7 +275,7 @@ func newCallCmd() *cobra.Command {
275275
fmt.Fprintln(os.Stderr, "Error: entity name is required")
276276
fmt.Fprintln(
277277
os.Stderr,
278-
"Example: mcp call read_file npx -y @modelcontextprotocol/server-filesystem ~/Code",
278+
"Example: mcp call read_file npx -y @modelcontextprotocol/server-filesystem ~",
279279
)
280280
os.Exit(1)
281281
}
@@ -292,7 +292,7 @@ func newCallCmd() *cobra.Command {
292292
fmt.Fprintln(os.Stderr, "Error: command to execute is required when using stdio transport")
293293
fmt.Fprintln(
294294
os.Stderr,
295-
"Example: mcp call read_file npx -y @modelcontextprotocol/server-filesystem ~/Code",
295+
"Example: mcp call read_file npx -y @modelcontextprotocol/server-filesystem ~",
296296
)
297297
os.Exit(1)
298298
}
@@ -350,7 +350,7 @@ func newGetPromptCmd() *cobra.Command {
350350
fmt.Fprintln(os.Stderr, "Error: prompt name is required")
351351
fmt.Fprintln(
352352
os.Stderr,
353-
"Example: mcp get-prompt read_file npx -y @modelcontextprotocol/server-filesystem ~/Code",
353+
"Example: mcp get-prompt read_file npx -y @modelcontextprotocol/server-filesystem ~",
354354
)
355355
os.Exit(1)
356356
}
@@ -384,7 +384,7 @@ func newGetPromptCmd() *cobra.Command {
384384
fmt.Fprintln(os.Stderr, "Error: prompt name is required")
385385
fmt.Fprintln(
386386
os.Stderr,
387-
"Example: mcp get-prompt read_file npx -y @modelcontextprotocol/server-filesystem ~/Code",
387+
"Example: mcp get-prompt read_file npx -y @modelcontextprotocol/server-filesystem ~",
388388
)
389389
os.Exit(1)
390390
}
@@ -428,7 +428,7 @@ func newReadResourceCmd() *cobra.Command {
428428
fmt.Fprintln(os.Stderr, "Error: resource name is required")
429429
fmt.Fprintln(
430430
os.Stderr,
431-
"Example: mcp read-resource npx -y @modelcontextprotocol/server-filesystem ~/Code",
431+
"Example: mcp read-resource npx -y @modelcontextprotocol/server-filesystem ~",
432432
)
433433
os.Exit(1)
434434
}
@@ -462,7 +462,7 @@ func newReadResourceCmd() *cobra.Command {
462462
fmt.Fprintln(os.Stderr, "Error: resource name is required")
463463
fmt.Fprintln(
464464
os.Stderr,
465-
"Example: mcp read-resource npx -y @modelcontextprotocol/server-filesystem ~/Code",
465+
"Example: mcp read-resource npx -y @modelcontextprotocol/server-filesystem ~",
466466
)
467467
os.Exit(1)
468468
}
@@ -519,7 +519,7 @@ func newShellCmd() *cobra.Command { //nolint:gocyclo
519519

520520
if len(parsedArgs) == 0 {
521521
fmt.Fprintln(os.Stderr, "Error: command to execute is required when using the shell")
522-
fmt.Fprintln(os.Stderr, "Example: mcp shell npx -y @modelcontextprotocol/server-filesystem ~/Code")
522+
fmt.Fprintln(os.Stderr, "Example: mcp shell npx -y @modelcontextprotocol/server-filesystem ~")
523523
os.Exit(1)
524524
}
525525

@@ -535,8 +535,9 @@ func newShellCmd() *cobra.Command { //nolint:gocyclo
535535
os.Exit(1)
536536
}
537537

538-
fmt.Println("mcp > connected to MCP server over stdio")
539-
fmt.Println("mcp > Type '/h' for help or '/q' to quit")
538+
fmt.Println("mcp tools shell")
539+
fmt.Println("connected to:", strings.Join(parsedArgs, " "))
540+
fmt.Println("\nmcp > Type '/h' for help or '/q' to quit")
540541

541542
line := liner.NewLiner()
542543
defer func() { _ = line.Close() }()

go.mod

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ module github.com/f/mcptools
22

33
go 1.24.1
44

5-
require github.com/spf13/cobra v1.9.1
5+
require (
6+
github.com/jedib0t/go-pretty/v6 v6.6.7
7+
github.com/peterh/liner v1.2.2
8+
github.com/spf13/cobra v1.9.1
9+
)
610

711
require (
812
github.com/inconshreveable/mousetrap v1.1.0 // indirect
9-
github.com/mattn/go-runewidth v0.0.3 // indirect
10-
github.com/peterh/liner v1.2.2 // indirect
13+
github.com/mattn/go-runewidth v0.0.16 // indirect
14+
github.com/rivo/uniseg v0.4.7 // indirect
1115
github.com/spf13/pflag v1.0.6 // indirect
12-
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect
16+
golang.org/x/sys v0.31.0 // indirect
17+
golang.org/x/term v0.30.0 // indirect
18+
golang.org/x/text v0.23.0 // indirect
1319
)

go.sum

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,36 @@
11
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
35
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
4-
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
6+
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
7+
github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
58
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
9+
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
10+
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
611
github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
712
github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
13+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
16+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
17+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
818
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
919
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
1020
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
1121
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
1222
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
13-
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
23+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
24+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1425
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
26+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
27+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
28+
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
29+
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
30+
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
31+
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
32+
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
33+
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
1534
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
35+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1636
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/jsonutils/jsonutils.go

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import (
77
"bytes"
88
"encoding/json"
99
"fmt"
10+
"os"
1011
"reflect"
1112
"sort"
1213
"strings"
1314
"text/tabwriter"
15+
16+
"golang.org/x/term"
1417
)
1518

1619
// OutputFormat represents the available output format options.
@@ -108,6 +111,7 @@ func formatTable(data any) (string, error) {
108111
return formatGenericMap(mapVal)
109112
}
110113

114+
// formatToolsList formats a list of tools as a table
111115
func formatToolsList(tools any) (string, error) {
112116
toolsSlice, ok := tools.([]any)
113117
if !ok {
@@ -119,11 +123,19 @@ func formatToolsList(tools any) (string, error) {
119123
}
120124

121125
var buf bytes.Buffer
122-
w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0)
126+
w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', tabwriter.StripEscape)
123127

124128
fmt.Fprintln(w, "NAME\tDESCRIPTION")
125129
fmt.Fprintln(w, "----\t-----------")
126130

131+
termWidth := getTermWidth()
132+
nameColWidth := 20 // Default name column width
133+
descColWidth := termWidth - nameColWidth - 5 // Leave some margin
134+
135+
if descColWidth < 10 {
136+
descColWidth = 40 // Minimum width if terminal is too narrow
137+
}
138+
127139
for _, t := range toolsSlice {
128140
tool, ok1 := t.(map[string]any)
129141
if !ok1 {
@@ -133,17 +145,77 @@ func formatToolsList(tools any) (string, error) {
133145
name, _ := tool["name"].(string)
134146
desc, _ := tool["description"].(string)
135147

136-
if len(desc) > 70 {
137-
desc = desc[:67] + "..."
148+
// Handle multiline description
149+
lines := wrapText(desc, descColWidth)
150+
151+
if len(lines) == 0 {
152+
fmt.Fprintf(w, "%s\t\n", name)
153+
continue
154+
}
155+
156+
// First line with name
157+
fmt.Fprintf(w, "%s\t%s\n", name, lines[0])
158+
159+
// Remaining lines with empty name column
160+
for _, line := range lines[1:] {
161+
fmt.Fprintf(w, "\t%s\n", line)
162+
}
163+
164+
// Add a blank line between entries
165+
if len(lines) > 1 {
166+
fmt.Fprintln(w, "\t")
138167
}
139-
140-
fmt.Fprintf(w, "%s\t%s\n", name, desc)
141168
}
142169

143170
_ = w.Flush()
144171
return buf.String(), nil
145172
}
146173

174+
// getTermWidth returns the terminal width or a default value if detection fails
175+
func getTermWidth() int {
176+
width, _, err := term.GetSize(int(os.Stdout.Fd()))
177+
if err != nil || width <= 0 {
178+
return 80 // Default width if terminal width cannot be determined
179+
}
180+
return width
181+
}
182+
183+
// wrapText wraps text to fit within a specified width
184+
func wrapText(text string, width int) []string {
185+
if text == "" {
186+
return []string{}
187+
}
188+
189+
words := strings.Fields(text)
190+
if len(words) == 0 {
191+
return []string{}
192+
}
193+
194+
var lines []string
195+
var currentLine string
196+
197+
for _, word := range words {
198+
// Check if adding this word would exceed the width
199+
if len(currentLine)+len(word)+1 > width && len(currentLine) > 0 {
200+
// Add current line to lines and start a new line
201+
lines = append(lines, currentLine)
202+
currentLine = word
203+
} else if len(currentLine) == 0 {
204+
currentLine = word
205+
} else {
206+
currentLine += " " + word
207+
}
208+
}
209+
210+
// Add the last line
211+
if len(currentLine) > 0 {
212+
lines = append(lines, currentLine)
213+
}
214+
215+
return lines
216+
}
217+
218+
// formatResourcesList formats a list of resources as a table
147219
func formatResourcesList(resources any) (string, error) {
148220
resourcesSlice, ok := resources.([]any)
149221
if !ok {
@@ -169,14 +241,16 @@ func formatResourcesList(resources any) (string, error) {
169241
name, _ := resource["name"].(string)
170242
resType, _ := resource["type"].(string)
171243
uri, _ := resource["uri"].(string)
172-
244+
245+
// Use the entire URI instead of truncating
173246
fmt.Fprintf(w, "%s\t%s\t%s\n", name, resType, uri)
174247
}
175248

176249
_ = w.Flush()
177250
return buf.String(), nil
178251
}
179252

253+
// formatPromptsList formats a list of prompts as a table
180254
func formatPromptsList(prompts any) (string, error) {
181255
promptsSlice, ok := prompts.([]any)
182256
if !ok {
@@ -188,11 +262,19 @@ func formatPromptsList(prompts any) (string, error) {
188262
}
189263

190264
var buf bytes.Buffer
191-
w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0)
265+
w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', tabwriter.StripEscape)
192266

193267
fmt.Fprintln(w, "NAME\tDESCRIPTION")
194268
fmt.Fprintln(w, "----\t-----------")
195269

270+
termWidth := getTermWidth()
271+
nameColWidth := 20 // Default name column width
272+
descColWidth := termWidth - nameColWidth - 5 // Leave some margin
273+
274+
if descColWidth < 10 {
275+
descColWidth = 40 // Minimum width if terminal is too narrow
276+
}
277+
196278
for _, p := range promptsSlice {
197279
prompt, ok1 := p.(map[string]any)
198280
if !ok1 {
@@ -202,11 +284,26 @@ func formatPromptsList(prompts any) (string, error) {
202284
name, _ := prompt["name"].(string)
203285
desc, _ := prompt["description"].(string)
204286

205-
if len(desc) > 70 {
206-
desc = desc[:67] + "..."
287+
// Handle multiline description
288+
lines := wrapText(desc, descColWidth)
289+
290+
if len(lines) == 0 {
291+
fmt.Fprintf(w, "%s\t\n", name)
292+
continue
293+
}
294+
295+
// First line with name
296+
fmt.Fprintf(w, "%s\t%s\n", name, lines[0])
297+
298+
// Remaining lines with empty name column
299+
for _, line := range lines[1:] {
300+
fmt.Fprintf(w, "\t%s\n", line)
301+
}
302+
303+
// Add a blank line between entries
304+
if len(lines) > 1 {
305+
fmt.Fprintln(w, "\t")
207306
}
208-
209-
fmt.Fprintf(w, "%s\t%s\n", name, desc)
210307
}
211308

212309
_ = w.Flush()

0 commit comments

Comments
 (0)