Skip to content

Commit f8dff5d

Browse files
authored
Merge pull request #57 from f/copilot/fix-56
Add streamable HTTP transport support with examples for local and remote servers
2 parents 4a29eb5 + 04174e3 commit f8dff5d

File tree

7 files changed

+200
-9
lines changed

7 files changed

+200
-9
lines changed

README.md

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,39 @@ mcp tools npx -y @modelcontextprotocol/server-filesystem ~
148148

149149
#### HTTP SSE Transport
150150

151-
Uses HTTP and Server-Sent Events (SSE) to communicate with an MCP server via JSON-RPC 2.0. This is useful for connecting to remote servers that implement the MCP protocol.
151+
Uses HTTP and Server-Sent Events (SSE) to communicate with an MCP server via JSON-RPC 2.0. This is useful for connecting to remote servers that implement the legacy MCP protocol.
152152

153153
```bash
154-
mcp tools http://localhost:3001/sse
154+
mcp tools --transport=sse http://localhost:3001/sse
155155

156156
# Example: Use the everything sample server
157157
# docker run -p 3001:3001 --rm -it tzolov/mcp-everything-server:v1
158158
```
159159

160160
_Note:_ HTTP SSE currently supports only MCP protocol version 2024-11-05.
161161

162+
#### Streamable HTTP Transport (Recommended)
163+
164+
Uses streamable HTTP to communicate with an MCP server via JSON-RPC 2.0. This is the modern, recommended approach for connecting to remote servers that implement the MCP protocol. It supports both streaming responses and simple request/response patterns.
165+
166+
```bash
167+
# Default transport for HTTP URLs (explicit flag not needed)
168+
mcp tools http://localhost:3000
169+
170+
# Explicitly specify streamable HTTP transport
171+
mcp tools --transport=http http://localhost:3000
172+
173+
# Examples with remote servers
174+
mcp tools https://api.example.com/mcp
175+
mcp tools --transport=http https://ne.tools
176+
```
177+
178+
_Benefits of Streamable HTTP:_
179+
- **Session Management**: Supports stateful connections with session IDs
180+
- **Resumability**: Can reconnect and resume interrupted sessions (when supported by server)
181+
- **Flexible Responses**: Supports both streaming and direct JSON responses
182+
- **Modern Protocol**: Uses the latest MCP transport specification
183+
162184
### Output Formats
163185

164186
MCP Tools supports three output formats to accommodate different needs:
@@ -373,11 +395,12 @@ mcp new tool:calculate --sdk=ts
373395
# Create a project with a specific transport type
374396
mcp new tool:calculate --transport=stdio
375397
mcp new tool:calculate --transport=sse
398+
mcp new tool:calculate --transport=http
376399
```
377400

378401
The scaffolding creates a complete project structure with:
379402

380-
- Server setup with chosen transport (stdio or SSE)
403+
- Server setup with chosen transport (stdio, SSE, or streamable HTTP)
381404
- TypeScript configuration with modern ES modules
382405
- Component implementations with proper MCP interfaces
383406
- Automatic wiring of imports and initialization
@@ -764,6 +787,48 @@ mcp guard --allow tools:search_files npx -y @modelcontextprotocol/server-filesys
764787
mcp guard --deny tools:write_*,delete_*,create_*,move_* npx -y @modelcontextprotocol/server-filesystem ~
765788
```
766789

790+
### Streamable HTTP Usage
791+
792+
Create and run a local streamable HTTP server:
793+
794+
```bash
795+
# Create a new MCP server with streamable HTTP transport
796+
mkdir my-http-server && cd my-http-server
797+
mcp new tool:example_tool --transport=http
798+
799+
# Install dependencies and build
800+
npm install && npm run build
801+
802+
# Start the server (will run on http://localhost:3000)
803+
npm start
804+
```
805+
806+
In a separate terminal, connect to your local server:
807+
808+
```bash
809+
# Connect to local streamable HTTP server
810+
mcp tools http://localhost:3000
811+
812+
# Call a tool on the local server
813+
mcp call example_tool --params '{"input": "test"}' http://localhost:3000
814+
815+
# Use with different output formats
816+
mcp tools --format pretty http://localhost:3000
817+
```
818+
819+
Connect to remote streamable HTTP servers:
820+
821+
```bash
822+
# Connect to a remote MCP server
823+
mcp tools https://api.example.com/mcp
824+
825+
# Use SSE transport for legacy servers
826+
mcp tools --transport=sse http://legacy-server.com/sse
827+
828+
# Example with authentication headers (when supported)
829+
mcp tools --transport=http https://authenticated-mcp-server.com
830+
```
831+
767832
### Script Integration
768833

769834
Using the proxy mode with a simple shell script:

cmd/mcptools/commands/new.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const (
1515
sdkTypeScript = "ts"
1616
transportStdio = "stdio"
1717
transportSSE = "sse"
18+
transportHTTP = "http"
1819
)
1920

2021
// NewCmd returns a new 'new' command for scaffolding MCP projects.
@@ -30,7 +31,7 @@ func NewCmd() *cobra.Command {
3031
Examples:
3132
mcp new tool:hello_world resource:file prompt:hello
3233
mcp new tool:hello_world --sdk=ts
33-
mcp new tool:hello_world --transport=stdio|sse`,
34+
mcp new tool:hello_world --transport=stdio|sse|http`,
3435
SilenceUsage: true,
3536
RunE: func(_ *cobra.Command, args []string) error {
3637
if len(args) == 0 {
@@ -48,8 +49,8 @@ Examples:
4849
}
4950

5051
// Validate transport flag
51-
if transportFlag != "" && transportFlag != transportStdio && transportFlag != transportSSE {
52-
return fmt.Errorf("unsupported transport: %s (supported options: stdio, sse)", transportFlag)
52+
if transportFlag != "" && transportFlag != transportStdio && transportFlag != transportSSE && transportFlag != transportHTTP {
53+
return fmt.Errorf("unsupported transport: %s (supported options: stdio, sse, http)", transportFlag)
5354
}
5455

5556
// Set default transport if not specified
@@ -84,7 +85,7 @@ Examples:
8485

8586
// Add flags
8687
cmd.Flags().StringVar(&sdkFlag, "sdk", "", "Specify the SDK to use (ts)")
87-
cmd.Flags().StringVar(&transportFlag, "transport", "", "Specify the transport to use (stdio, sse)")
88+
cmd.Flags().StringVar(&transportFlag, "transport", "", "Specify the transport to use (stdio, sse, http)")
8889

8990
return cmd
9091
}
@@ -128,6 +129,8 @@ func createProjectStructure(components map[string]string, sdk, transport string)
128129
var serverTemplateFile string
129130
if transport == transportSSE {
130131
serverTemplateFile = filepath.Join(templatesDir, "server_sse.ts")
132+
} else if transport == transportHTTP {
133+
serverTemplateFile = filepath.Join(templatesDir, "server_http.ts")
131134
} else {
132135
// Use stdio by default
133136
serverTemplateFile = filepath.Join(templatesDir, "server_stdio.ts")

cmd/mcptools/commands/root.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
FlagHelp = "--help"
1717
FlagHelpShort = "-h"
1818
FlagServerLogs = "--server-logs"
19+
FlagTransport = "--transport"
1920
)
2021

2122
// entity types.
@@ -34,6 +35,9 @@ var (
3435
ParamsString string
3536
// ShowServerLogs is a flag to show server logs.
3637
ShowServerLogs bool
38+
// TransportOption is the transport option for HTTP connections, valid values are "sse" and "http".
39+
// Default is "http" (streamable HTTP).
40+
TransportOption = "http"
3741
)
3842

3943
// RootCmd creates the root command.
@@ -48,6 +52,7 @@ It allows you to discover and call tools, list resources, and interact with MCP-
4852
cmd.PersistentFlags().StringVarP(&FormatOption, "format", "f", "table", "Output format (table, json, pretty)")
4953
cmd.PersistentFlags().
5054
StringVarP(&ParamsString, "params", "p", "{}", "JSON string of parameters to pass to the tool (for call command)")
55+
cmd.PersistentFlags().StringVar(&TransportOption, "transport", "http", "HTTP transport type (http, sse)")
5156

5257
return cmd
5358
}

cmd/mcptools/commands/utils.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@ var CreateClientFunc = func(args []string, _ ...client.ClientOption) (*client.Cl
4747
var err error
4848

4949
if len(args) == 1 && IsHTTP(args[0]) {
50-
c, err = client.NewSSEMCPClient(args[0])
50+
// Validate transport option for HTTP URLs
51+
if TransportOption != "http" && TransportOption != "sse" {
52+
return nil, fmt.Errorf("invalid transport option: %s (supported: http, sse)", TransportOption)
53+
}
54+
55+
if TransportOption == "sse" {
56+
c, err = client.NewSSEMCPClient(args[0])
57+
} else {
58+
// Default to streamable HTTP
59+
c, err = client.NewStreamableHttpClient(args[0])
60+
}
5161
if err != nil {
5262
return nil, err
5363
}
@@ -91,6 +101,7 @@ var CreateClientFunc = func(args []string, _ ...client.ClientOption) (*client.Cl
91101

92102
// ProcessFlags processes command line flags, sets the format option, and returns the remaining
93103
// arguments. Supported format options: json, pretty, and table.
104+
// Supported transport options: http and sse.
94105
//
95106
// For example, if the input arguments are ["tools", "--format", "pretty", "npx", "-y",
96107
// "@modelcontextprotocol/server-filesystem", "~"], it would return ["npx", "-y",
@@ -104,6 +115,9 @@ func ProcessFlags(args []string) []string {
104115
case (args[i] == FlagFormat || args[i] == FlagFormatShort) && i+1 < len(args):
105116
FormatOption = args[i+1]
106117
i += 2
118+
case args[i] == FlagTransport && i+1 < len(args):
119+
TransportOption = args[i+1]
120+
i += 2
107121
case args[i] == FlagServerLogs:
108122
ShowServerLogs = true
109123
i++

cmd/mcptools/transport_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/f/mcptools/cmd/mcptools/commands"
7+
)
8+
9+
func TestTransportFlag(t *testing.T) {
10+
// Save original values
11+
origTransport := commands.TransportOption
12+
13+
// Test default value
14+
if commands.TransportOption != "http" {
15+
t.Errorf("Expected default transport to be 'http', got '%s'", commands.TransportOption)
16+
}
17+
18+
// Test ProcessFlags with transport flag
19+
args := []string{"tools", "--transport", "sse", "http://localhost:3000"}
20+
remainingArgs := commands.ProcessFlags(args)
21+
22+
expectedArgs := []string{"tools", "http://localhost:3000"}
23+
if len(remainingArgs) != len(expectedArgs) {
24+
t.Errorf("Expected %d args, got %d", len(expectedArgs), len(remainingArgs))
25+
}
26+
27+
for i, arg := range expectedArgs {
28+
if remainingArgs[i] != arg {
29+
t.Errorf("Expected arg %d to be '%s', got '%s'", i, arg, remainingArgs[i])
30+
}
31+
}
32+
33+
if commands.TransportOption != "sse" {
34+
t.Errorf("Expected transport to be 'sse', got '%s'", commands.TransportOption)
35+
}
36+
37+
// Restore original values
38+
commands.TransportOption = origTransport
39+
}
40+
41+
func TestIsHTTP(t *testing.T) {
42+
testCases := []struct {
43+
url string
44+
expected bool
45+
}{
46+
{"http://localhost:3000", true},
47+
{"https://example.com", true},
48+
{"localhost:3000", true},
49+
{"stdio", false},
50+
{"", false},
51+
{"file:///path", false},
52+
}
53+
54+
for _, tc := range testCases {
55+
result := commands.IsHTTP(tc.url)
56+
if result != tc.expected {
57+
t.Errorf("IsHTTP(%s) = %v, expected %v", tc.url, result, tc.expected)
58+
}
59+
}
60+
}

templates/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ mcp new tool:hello_world resource:file prompt:hello
2727
# Create a project with a specific SDK (currently only TypeScript/ts supported)
2828
mcp new tool:hello_world --sdk=ts
2929

30-
# Create a project with a specific transport (stdio or sse)
30+
# Create a project with a specific transport (stdio, sse, or http)
3131
mcp new tool:hello_world --transport=stdio
3232
mcp new tool:hello_world --transport=sse
33+
mcp new tool:hello_world --transport=http
3334
```
3435

3536
## Available Templates
@@ -41,6 +42,7 @@ mcp new tool:hello_world --transport=sse
4142
- **prompt**: Prompt implementation template
4243
- **server_stdio**: Server with stdio transport
4344
- **server_sse**: Server with SSE transport
45+
- **server_http**: Server with streamable HTTP transport
4446
- **full_server**: Complete server with all three capabilities
4547

4648
## Project Structure

templates/ts/server_http.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3+
import express from "express";
4+
import { z } from "zod";
5+
import { randomUUID } from "crypto";
6+
7+
// Initialize server
8+
const server = new McpServer({
9+
name: "PROJECT_NAME",
10+
version: "1.0.0"
11+
});
12+
13+
// Initialize components here
14+
// COMPONENT_INITIALIZATION
15+
16+
// Setup Express app
17+
const app = express();
18+
19+
// Enable JSON parsing for request bodies
20+
app.use(express.json());
21+
22+
// Create a streamable HTTP transport
23+
const transport = new StreamableHTTPServerTransport({
24+
// Enable session management with auto-generated UUIDs
25+
sessionIdGenerator: () => randomUUID(),
26+
// Optional: Enable JSON response mode for simple request/response
27+
enableJsonResponse: false
28+
});
29+
30+
// Handle all HTTP methods on the root path
31+
app.all("/", async (req, res) => {
32+
await transport.handleRequest(req, res, req.body);
33+
});
34+
35+
// Connect the server to the transport
36+
await server.connect(transport);
37+
38+
// Start HTTP server
39+
const port = 3000;
40+
app.listen(port, () => {
41+
console.log(`MCP server running on http://localhost:${port}`);
42+
});

0 commit comments

Comments
 (0)