Skip to content
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ coverage.txt
dist/*
_site/*
devtui
.idea
.idea
.worktrees
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,40 @@ Or download executable from [GitHub Releases](https://github.com/skatkov/devtui/

---

## MCP

Run DevTUI as an MCP server over stdio:

```bash
devtui mcp
```

### MCP build
To include the MCP server, build with:

```bash
go build -tags mcp ./...
```

### Claude Code

Add DevTUI as an MCP server in `~/.claude/claude.json`:

```json
{
"mcpServers": {
"devtui": {
"command": "devtui",
"args": ["mcp"]
}
}
}
```

Make sure `devtui` is on your PATH, then restart Claude Code.

---

## Documentation Generator

DevTUI includes automated documentation generators for both CLI and TUI interfaces (not complete yet, though).
Expand Down
26 changes: 26 additions & 0 deletions cmd/mcp_disabled.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build !mcp

package cmd

import (
"errors"
"fmt"

"github.com/spf13/cobra"
)

var mcpCmd = &cobra.Command{
Use: "mcp",
Short: "Run DevTUI as an MCP stdio server",
RunE: func(cmd *cobra.Command, args []string) error {
message := "mcp disabled; rebuild with -tags mcp"
if _, err := fmt.Fprintln(cmd.ErrOrStderr(), message); err != nil {
return err
}
return errors.New(message)
},
}

func init() {
rootCmd.AddCommand(mcpCmd)
}
25 changes: 25 additions & 0 deletions cmd/mcp_disabled_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build !mcp

package cmd

import (
"bytes"
"strings"
"testing"
)

func TestMCPDisabledMessage(t *testing.T) {
cmd := GetRootCmd()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"mcp"})

err := cmd.Execute()
if err == nil {
t.Fatalf("expected error when mcp tag disabled")
}
if !strings.Contains(buf.String(), "mcp disabled") {
t.Fatalf("expected disabled message, got: %s", buf.String())
}
}
33 changes: 33 additions & 0 deletions cmd/mcp_enabled.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build mcp

package cmd

import (
mcp "github.com/skatkov/devtui-mcp"
"github.com/spf13/cobra"
)

var mcpCmd = &cobra.Command{
Use: "mcp",
Short: "Run DevTUI as an MCP stdio server",
RunE: func(cmd *cobra.Command, args []string) error {
tools := mcp.BuildTools(GetRootCmd())
server := mcp.NewServer(mcp.ServerConfig{
Tools: tools,
ServerInfo: mcp.ServerInfo{
Name: "devtui",
Version: GetVersion(),
},
Call: func(_ string, params mcp.CallParams) (string, error) {
root := GetRootCmd()
return mcp.ExecuteTool(root, params)
},
})

return mcp.ServeStdio(server, cmd.InOrStdin(), cmd.OutOrStdout())
},
}

func init() {
rootCmd.AddCommand(mcpCmd)
}
40 changes: 40 additions & 0 deletions cmd/mcp_enabled_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//go:build mcp

package cmd

import (
"bytes"
"testing"
)

func TestMCPCommandListsTools(t *testing.T) {
cmd := GetRootCmd()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetIn(bytes.NewBufferString("{\"id\":1,\"method\":\"tools/list\"}\n"))
cmd.SetArgs([]string{"mcp"})

if err := cmd.Execute(); err != nil {
t.Fatalf("execute failed: %v", err)
}
if buf.Len() == 0 {
t.Fatalf("expected output")
}
}

func TestMCPCommandInitialize(t *testing.T) {
cmd := GetRootCmd()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetIn(bytes.NewBufferString("{\"id\":1,\"method\":\"initialize\"}\n"))
cmd.SetArgs([]string{"mcp"})

if err := cmd.Execute(); err != nil {
t.Fatalf("execute failed: %v", err)
}
if buf.Len() == 0 {
t.Fatalf("expected output")
}
}
5 changes: 5 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ var (
date = "unknown"
)

// GetVersion returns the raw version string.
func GetVersion() string {
return version
}

// GetVersionShort returns a short version string suitable for single-line output.
func GetVersionShort() string {
return fmt.Sprintf("%s (commit: %s, built: %s)", version, commit, date)
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ require (
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.2.4
github.com/skatkov/devtui-mcp v0.0.0-20260130223332-c6b090edb9ea
github.com/spf13/cobra v1.10.2
github.com/tiagomelo/go-clipboard v0.1.2
github.com/twpayne/go-jsonstruct/v3 v3.3.0
Expand Down Expand Up @@ -98,7 +99,7 @@ require (
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/skatkov/devtui-mcp v0.0.0-20260130223332-c6b090edb9ea h1:ZF6Z+sJL5Hgia7l/+uS/ueQLXOouAq+qDljWyGDfRao=
github.com/skatkov/devtui-mcp v0.0.0-20260130223332-c6b090edb9ea/go.mod h1:T/ePBgccAF4mH9BNZPbGPiycoOB8CCvFlQYw9sds1fU=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
Expand Down Expand Up @@ -232,8 +234,8 @@ github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down