CleverChatty now supports Streamable HTTP for MCP servers!

CleverChatty now supports Streamable HTTP for MCP servers!

CleverChatty, a lightweight AI Chat tool supporting multiple LLM providers, now includes support for Streamable HTTP with MCP servers. This update enables more flexible and efficient communication with AI models, making integration with custom tools and services even smoother.


🌐 What is CleverChatty?

CleverChatty is a minimalist AI chat interface that works with various large language model (LLM) providers — including OpenAI, Anthropic, Google, and local models like Ollama. It’s designed for users and developers who want a simple, extensible tool that supports MCP-based tool usage.

Until now, CleverChatty only supported STDIO and SSE (Server-Sent Events) as transport protocols for connecting with MCP servers. With the latest update, it now supports Streamable HTTP, expanding compatibility and flexibility.


🚀 How to Use Streamable HTTP in CleverChatty

Streamable HTTP is now the default transport method when the transport field is omitted but a url is present in the config file.

To use SSE, explicitly set "transport": "sse".

✅ Example Configuration

{
    "log_file_path": "",
    "model": "ollama:qwen2.5:3b",
    "mcpServers": {
        "http_streaming_weather_server": {
            "url": "http://weather-service:8000/mcp",
            "headers": []
        },
        "sse_weather_server": {
            "url": "http://weather-service:8001/sse",
            "transport": "sse",
            "headers": []
        },
        "stdio_weather_server": {
            "command": "uv",
            "args": ["weather-service.py"]
        }
    }
}

🧪 Example MCP Server: Supports STDIO, SSE, and Streamable HTTP

The example below demonstrates an MCP server that supports all three transports. It also shows how to write a universal server capable of handling multiple protocols, making it perfect for testing CleverChatty.

✅ This server also includes a basic authentication layer and a command-execution tool for Linux/Mac.

package main

import (
   "context"
   "fmt"
   "net/http"
   "os"
   "os/exec"
   "path/filepath"

   "github.com/mark3labs/mcp-go/mcp"
   "github.com/mark3labs/mcp-go/server"
)

var requireAuth = false

func main() {
   if len(os.Args) < 2 {
       binaryName := filepath.Base(os.Args[0])
       fmt.Println("Usage: ./" + binaryName + " <TRANSPORT> [OPTIONS]")
       fmt.Println("Usage: ./" + binaryName + " sse HOST:PORT")
       fmt.Println("Usage: ./" + binaryName + " http_streaming HOST:PORT")
       fmt.Println("Usage: ./" + binaryName + " stdio")
       os.Exit(1)
   }

   transport := os.Args[1]

   switch transport {
   case "stdio":
       runAsStdio()

   case "sse":
       if len(os.Args) < 3 {
           fmt.Println("sse transport requires a host and port argument (e.g., 0.0.0.0:8080)")
           os.Exit(1)
       }
       host_and_port := os.Args[2]
       runAsSSE(host_and_port)

   case "http_streaming":
       if len(os.Args) < 3 {
           fmt.Println("http streaming transport requires a host and port argument (e.g., 0.0.0.0:8080)")
           os.Exit(1)
       }
       host_and_port := os.Args[2]
       runAsHTTPStreaming(host_and_port)
   default:
       fmt.Printf("Unknown transport: %s\n", transport)
       os.Exit(3)
   }

}

func runAsStdio() {
   if err := server.ServeStdio(createServer()); err != nil {
       fmt.Printf("😡 Server error: %v\n", err)
   }
}
func runAsSSE(host_and_port string) {
   // for SSE we require auth
   requireAuth = true
   // Start the stdio server
   sObj := server.NewSSEServer(createServer(),
       server.WithSSEContextFunc(server.SSEContextFunc(httpContextFunction)))

   fmt.Println("🚀 SSE Server started")

   if err := sObj.Start(host_and_port); err != nil {
       fmt.Printf("😡 Server error: %v\n", err)
   }
   fmt.Println("👋 Server stopped")
}

func runAsHTTPStreaming(host_and_port string) {
   // for SSE we require auth
   requireAuth = true
   // Start the stdio server
   sObj := server.NewStreamableHTTPServer(createServer(),
       server.WithHTTPContextFunc(server.HTTPContextFunc(httpContextFunction)))

   fmt.Println("🚀 Streamable HTTP Server started")

   if err := sObj.Start(host_and_port); err != nil {
       fmt.Printf("😡 Server error: %v\n", err)
   }
   fmt.Println("👋 Server stopped")
}

// All the code below is not related to MCP server transport
func createServer() *server.MCPServer {
   // Create MCP server
   s := server.NewMCPServer(
       "Server to manage a Linux instance",
       "1.0.0",
   )

   execTool := mcp.NewTool("exec_cmd",
       mcp.WithDescription("Execute a Linux command with optional working directory"),
       mcp.WithString("command",
           mcp.Required(),
           mcp.Description("The full shell command to execute"),
       ),
       mcp.WithString("working_dir",
           mcp.Description("Optional working directory where the command should run"),
       ),
   )

   s.AddTool(execTool, RequireAuth(execCmdHandler))
   return s
}

// This function is used to extract the token from the HTTP request context.
// It is used in the SSE and HTTP streaming transports to authenticate requests.
func httpContextFunction(ctx context.Context, r *http.Request) context.Context {
   // Extract the Authorization header from the request and get the token from it
   header := r.Header.Get("Authorization")

   if header == "" {
       return ctx
   }

   // get token after Bearer
   token := header[len("Bearer "):]
   if token == "" {
       return ctx
   }
   fmt.Printf("😎 Token: %s\n", token)
   // add this token to the context. We will check it later
   ctx = context.WithValue(ctx, "token", token)
   return ctx
}

// RequireAuth is a middleware that checks if the request is authenticated.
func RequireAuth(handler server.ToolHandlerFunc) server.ToolHandlerFunc {
   return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
       if !requireAuth {
           // If we don't require auth, just call the handler
           return handler(ctx, request)
       }
       token, ok := ctx.Value("token").(string)

       if !ok || token != "expected-token" { // or validate it
           return mcp.NewToolResultError("unauthorized"), nil
       }
       return handler(ctx, request)
   }
}

func execCmdHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
   args, ok := request.Params.Arguments.(map[string]interface{})
   if !ok {
       return mcp.NewToolResultError("invalid arguments"), nil
   }
   cmdStr, ok := args["command"].(string)
   if !ok || cmdStr == "" {
       return mcp.NewToolResultError("command is required"), nil
   }

   // Optional working_dir
   var workingDir string
   if wd, ok := args["working_dir"].(string); ok {
       workingDir = wd
   }

   // Use "sh -c" to allow full shell command with arguments and operators
   cmd := exec.Command("sh", "-c", cmdStr)

   if workingDir != "" {
       cmd.Dir = workingDir
   }

   output, err := cmd.CombinedOutput()

   if err != nil {
       // Include both the error and output for context
       return mcp.NewToolResultError(fmt.Sprintf("execution failed: %v\n%s", err, output)), nil
   }

   return mcp.NewToolResultText(string(output)), nil
}

You can run this server in different modes using:

go run main.go http_streaming 0.0.0.0:8080
# or
go run main.go sse 0.0.0.0:8080
# or compile and run for stdio
go build -o myserver main.go
./myserver stdio

🛠️ Setting up CleverChatty with Streamable HTTP

To test CleverChatty with your new Streamable HTTP MCP server:

1. Create a config.json

{
    "log_file_path": "",
    "model": "ollama:qwen2.5:3b",
    "mcpServers": {
        "linux_server": {
            "url": "http://localhost:8080/mcp",
            "headers": []
        }
    }
}

2. Start the MCP server

go run main.go http_streaming 0.0.0.0:8080

3. Run CleverChatty

go run github.com/gelembjuk/cleverchatty-cli@latest --config config.json

Or install and run:

go install github.com/gelembjuk/cleverchatty-cli@latest
cleverchatty --config config.json

⚠️ Platform Note

This MCP server uses sh -c for executing shell commands, so it works only on Linux or macOS. To run it on Windows, you’ll need to modify the execCmdHandler function to use a Windows-compatible command execution method.


With Streamable HTTP support now built in, CleverChatty becomes even more versatile for developers building intelligent, tool-using AI agents. Whether you prefer SSE, STDIO, or now Streamable HTTP — you’ve got the freedom to choose what fits your deployment best.

Previous Post:

MCP, LLM
What’s Missing in MCP
9 June 2025

Over the past couple of months, I’ve been experimenting with the Model Context Protocol (MCP) — building AI agents and tools around it. While the experience has been promising, I’ve noticed a few areas where MCP could be expanded or improved.

Continue Reading
What’s Missing in MCP

Next Post:

MCP, LLM
Integrating Mem0 (mem-zero) with CleverChatty
18 June 2025

Integrating Mem0 memory model with CleverChatty-CLI for AI assistants.

Continue Reading
Integrating Mem0 (mem-zero) with CleverChatty