diff --git a/.github/resources/web-interface.png b/.github/resources/web-interface.png
new file mode 100644
index 0000000..17a8171
Binary files /dev/null and b/.github/resources/web-interface.png differ
diff --git a/README.md b/README.md
index ee33e6f..049efed 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@
- [Output Formats](#output-formats)
- [Commands](#commands)
- [Interactive Shell](#interactive-shell)
+ - [Web Interface](#web-interface)
- [Project Scaffolding](#project-scaffolding)
- [Server Aliases](#server-aliases)
- [LLM Apps Config Management](#llm-apps-config-management)
@@ -112,6 +113,7 @@ Available Commands:
get-prompt Get a prompt on the MCP server
read-resource Read a resource on the MCP server
shell Start an interactive shell for MCP commands
+ web Start a web interface for MCP commands
mock Create a mock MCP server with tools, prompts, and resources
proxy Proxy MCP tool requests to shell scripts
alias Manage MCP server aliases
@@ -320,6 +322,36 @@ Special Commands:
/q, /quit, exit Exit the shell
```
+### Web Interface
+
+MCP Tools provides a web interface for interacting with MCP servers through a browser-based UI:
+
+```bash
+# Start a web interface for a filesystem server on default port (41999)
+mcp web npx -y @modelcontextprotocol/server-filesystem ~
+
+# Use a custom port
+mcp web --port 8080 docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server
+
+# Use SSE
+mcp web https://ne.tools
+```
+
+The web interface includes:
+
+- A sidebar listing all available tools, resources, and prompts
+- Form-based and JSON-based parameter editing
+- Formatted and raw JSON response views
+- Interactive parameter forms automatically generated from tool schemas
+- Support for complex parameter types (arrays, objects, nested structures)
+- Direct API access for tool calling
+
+Once started, you can access the interface by opening `http://localhost:41999` (or your custom port) in a browser.
+
+
+
+
+
### Project Scaffolding
MCP Tools provides a scaffolding feature to quickly create new MCP servers with TypeScript:
diff --git a/cmd/mcptools/commands/guard.go b/cmd/mcptools/commands/guard.go
index aef30e5..5fb763b 100644
--- a/cmd/mcptools/commands/guard.go
+++ b/cmd/mcptools/commands/guard.go
@@ -165,6 +165,7 @@ func processPatternString(patternsStr string, patternMap map[string][]string) {
patternValue := parts[1]
// Map entity type to known types
+ //nolint:goconst // Using literals directly for readability
switch entityType {
case "tool", "tools":
patternMap[EntityTypeTool] = append(patternMap[EntityTypeTool], patternValue)
diff --git a/cmd/mcptools/commands/web.go b/cmd/mcptools/commands/web.go
new file mode 100644
index 0000000..4406700
--- /dev/null
+++ b/cmd/mcptools/commands/web.go
@@ -0,0 +1,1306 @@
+package commands
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
+ "sync"
+
+ "github.com/f/mcptools/pkg/client"
+ "github.com/spf13/cobra"
+)
+
+// WebCmd creates the web command.
+func WebCmd() *cobra.Command {
+ return &cobra.Command{
+ Use: "web [command args...]",
+ Short: "Start a web interface for MCP commands",
+ DisableFlagParsing: true,
+ SilenceUsage: true,
+ Run: func(thisCmd *cobra.Command, args []string) {
+ if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) {
+ _ = thisCmd.Help()
+ return
+ }
+
+ cmdArgs := args
+ parsedArgs := []string{}
+ port := "41999" // Default port
+
+ for i := 0; i < len(cmdArgs); i++ {
+ switch {
+ case (cmdArgs[i] == "--port" || cmdArgs[i] == "-p") && i+1 < len(cmdArgs):
+ port = cmdArgs[i+1]
+ i++
+ case cmdArgs[i] == FlagServerLogs:
+ ShowServerLogs = true
+ default:
+ parsedArgs = append(parsedArgs, cmdArgs[i])
+ }
+ }
+
+ if len(parsedArgs) == 0 {
+ fmt.Fprintln(os.Stderr, "Error: command to execute is required when using the web interface")
+ fmt.Fprintln(os.Stderr, "Example: mcp web npx -y @modelcontextprotocol/server-filesystem ~")
+ os.Exit(1)
+ }
+
+ mcpClient, clientErr := CreateClientFunc(parsedArgs, client.CloseTransportAfterExecute(false))
+ if clientErr != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", clientErr)
+ os.Exit(1)
+ }
+
+ _, listErr := mcpClient.ListTools()
+ if listErr != nil {
+ fmt.Fprintf(os.Stderr, "Error connecting to MCP server: %v\n", listErr)
+ os.Exit(1)
+ }
+
+ fmt.Fprintf(thisCmd.OutOrStdout(), "mcp > Starting MCP Tools Web Interface (%s)\n", Version)
+ fmt.Fprintf(thisCmd.OutOrStdout(), "mcp > Connected to Server: %s\n", strings.Join(parsedArgs, " "))
+ fmt.Fprintf(thisCmd.OutOrStdout(), "mcp > Web server running at http://localhost:%s\n", port)
+
+ // Web server handler
+ mux := http.NewServeMux()
+
+ // Create a client cache that can be safely shared across goroutines
+ clientCache := &MCPClientCache{
+ client: mcpClient,
+ mutex: &sync.Mutex{},
+ }
+
+ // Serve static files
+ mux.HandleFunc("/", handleIndex())
+ mux.HandleFunc("/api/tools", handleTools(clientCache))
+ mux.HandleFunc("/api/resources", handleResources(clientCache))
+ mux.HandleFunc("/api/prompts", handlePrompts(clientCache))
+ mux.HandleFunc("/api/call", handleCall(clientCache))
+
+ // Start the server
+ //nolint:gosec // Timeouts not implemented for this development/internal tool
+ err := http.ListenAndServe(":"+port, mux)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error starting web server: %v\n", err)
+ os.Exit(1)
+ }
+ },
+ }
+}
+
+// MCPClientCache provides thread-safe access to the MCP client.
+type MCPClientCache struct {
+ client *client.Client
+ mutex *sync.Mutex
+}
+
+// handleIndex serves the main web interface.
+func handleIndex() http.HandlerFunc {
+ //nolint:revive // Parameter r is required by http.HandlerFunc signature
+ return func(w http.ResponseWriter, r *http.Request) {
+ // For simplicity, we'll embed a basic HTML page directly
+ // In a production app, we'd use proper templates and static files
+ html := `
+
+
+
+
+
+ MCP Tools
+
+
+
+
+
+
+
+
+
Select an item from the sidebar
+
+
+
+
+
+
+
+
+
+
+`
+ w.Header().Set("Content-Type", "text/html")
+ //nolint:errcheck,gosec // No need to handle error from Write in this context
+ w.Write([]byte(html))
+ }
+}
+
+// handleTools handles API requests for listing tools.
+func handleTools(cache *MCPClientCache) http.HandlerFunc {
+ //nolint:revive // Parameter r is required by http.HandlerFunc signature
+ return func(w http.ResponseWriter, r *http.Request) {
+ cache.mutex.Lock()
+ resp, err := cache.client.ListTools()
+ cache.mutex.Unlock()
+
+ w.Header().Set("Content-Type", "application/json")
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "error": err.Error(),
+ })
+ return
+ }
+
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "result": resp,
+ })
+ }
+}
+
+// handleResources handles API requests for listing resources.
+func handleResources(cache *MCPClientCache) http.HandlerFunc {
+ //nolint:revive // Parameter r is required by http.HandlerFunc signature
+ return func(w http.ResponseWriter, r *http.Request) {
+ cache.mutex.Lock()
+ resp, err := cache.client.ListResources()
+ cache.mutex.Unlock()
+
+ w.Header().Set("Content-Type", "application/json")
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "error": err.Error(),
+ })
+ return
+ }
+
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "result": resp,
+ })
+ }
+}
+
+// handlePrompts handles API requests for listing prompts.
+func handlePrompts(cache *MCPClientCache) http.HandlerFunc {
+ //nolint:revive // Parameter r is required by http.HandlerFunc signature
+ return func(w http.ResponseWriter, r *http.Request) {
+ cache.mutex.Lock()
+ resp, err := cache.client.ListPrompts()
+ cache.mutex.Unlock()
+
+ w.Header().Set("Content-Type", "application/json")
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "error": err.Error(),
+ })
+ return
+ }
+
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "result": resp,
+ })
+ }
+}
+
+// handleCall handles API requests for calling tools/resources/prompts.
+func handleCall(cache *MCPClientCache) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ return
+ }
+
+ var requestData struct {
+ Params map[string]interface{} `json:"params"`
+ Type string `json:"type"`
+ Name string `json:"name"`
+ }
+
+ err := json.NewDecoder(r.Body).Decode(&requestData)
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "error": "Invalid request: " + err.Error(),
+ })
+ return
+ }
+
+ var resp map[string]interface{}
+ var callErr error
+
+ cache.mutex.Lock()
+ defer cache.mutex.Unlock()
+
+ switch requestData.Type {
+ case "tool":
+ resp, callErr = cache.client.CallTool(requestData.Name, requestData.Params)
+ case "resource":
+ resp, callErr = cache.client.ReadResource(requestData.Name)
+ case "prompt":
+ resp, callErr = cache.client.GetPrompt(requestData.Name)
+ default:
+ w.WriteHeader(http.StatusBadRequest)
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "error": "Invalid entity type: " + requestData.Type,
+ })
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ if callErr != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "error": callErr.Error(),
+ })
+ return
+ }
+
+ //nolint:errcheck,gosec // No need to handle error from Encode in this context
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "result": resp,
+ })
+ }
+}
diff --git a/cmd/mcptools/main.go b/cmd/mcptools/main.go
index 2b7c046..2d2c91a 100644
--- a/cmd/mcptools/main.go
+++ b/cmd/mcptools/main.go
@@ -34,6 +34,7 @@ func main() {
commands.GetPromptCmd(),
commands.ReadResourceCmd(),
commands.ShellCmd(),
+ commands.WebCmd(),
commands.MockCmd(),
commands.ProxyCmd(),
commands.AliasCmd(),
diff --git a/mcptools b/mcptools
new file mode 100755
index 0000000..8b54a6b
Binary files /dev/null and b/mcptools differ