Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

A CLI tool to scaffold Model Context Protocol (MCP) server projects in Go and other languages. It generates ready-to-use MCP server templates with support for multiple transports, Docker, and example resources/tools.

**Current Version:** v0.4.1 (latest release)
**Current Version:** v0.4.2 (latest release)

> Learn more about the Model Context Protocol at the [official introduction page](https://modelcontextprotocol.io/introduction).

Expand Down Expand Up @@ -114,7 +114,7 @@ A generated Node.js MCP server project includes:

Run `go test ./... -cover` to execute the unit tests. Overall coverage should remain above 85%.
Recent additions include tests for `internal/commands/test.go` and error handling cases in `internal/generators/node_test.go` to ensure the CLI testing workflow behaves as expected.
See [the testing guide](doc/testing.md) for more details on running the tests for **mcpcli v0.4.1 (latest)**.
See [the testing guide](doc/testing.md) for more details on running the tests for **mcpcli v0.4.2 (latest)**.
All contributions must maintain this minimum coverage level.

## Contributing
Expand Down
6 changes: 3 additions & 3 deletions docs/features.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<div class="container">
<h2 class="section-title">Features</h2>
<p class="section-subtitle">Everything you need to build MCP servers quickly and efficiently</p>
<p><strong>Version:</strong> v0.4.1 (latest release)</p>
<p><strong>Version:</strong> v0.4.2 (latest release)</p>
<div class="features-grid">
<div class="feature-card">
<span class="badge-implemented">Implemented</span>
Expand All @@ -42,10 +42,10 @@ <h3>Multiple Languages</h3>
</div>
<div class="feature-card">
<span class="badge-implemented">Stdio</span>
<span class="badge-planned">REST, WebSocket (planned)</span>
<span class="badge-implemented">REST, WebSocket</span>
<div class="feature-icon">🔗</div>
<h3>Flexible Transports</h3>
<p>Stdio <span class="badge-implemented">(available)</span>, REST and WebSocket <span class="badge-planned">(planned)</span> transport methods for your server.</p>
<p>Stdio, REST and WebSocket <span class="badge-implemented">(available)</span> transport methods for your server.</p>
</div>
<div class="feature-card">
<span class="badge-implemented">Implemented</span>
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<div class="container">
<h2 class="section-title">Getting Started</h2>
<p class="section-subtitle">Get up and running in minutes</p>
<p><strong>Version:</strong> v0.4.1 (latest release)</p>
<p><strong>Version:</strong> v0.4.2 (latest release)</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
Expand Down
6 changes: 3 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ <h3>Multiple Languages</h3>
</div>
<div class="feature-card">
<span class="badge-implemented">Stdio</span>
<span class="badge-planned">REST, WebSocket (planned)</span>
<span class="badge-implemented">REST, WebSocket</span>
<div class="feature-icon">🔗</div>
<h3>Flexible Transports</h3>
<p>Stdio <span class="badge-implemented">(available)</span>, REST and WebSocket <span class="badge-planned">(planned)</span> transport methods for your server.</p>
<p>Stdio, REST and WebSocket <span class="badge-implemented">(available)</span> transport methods for your server.</p>
</div>
<div class="feature-card">
<span class="badge-implemented">Implemented</span>
Expand Down Expand Up @@ -88,7 +88,7 @@ <h3>Extensible</h3>
<div class="container">
<h2 class="section-title">Getting Started</h2>
<p class="section-subtitle">Get up and running in minutes</p>
<p><strong>Version:</strong> v0.4.1 (latest release)</p>
<p><strong>Version:</strong> v0.4.2 (latest release)</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
Expand Down
2 changes: 1 addition & 1 deletion internal/core/doc.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package core defines configuration and data types used by mcpcli.
// Version information (latest tag v0.4.1) is propagated through this package to templates.
// Version information (latest tag v0.4.2) is propagated through this package to templates.
// This ensures generated projects reference the latest published version.
package core
6 changes: 3 additions & 3 deletions internal/generators/generator_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ func TestGenerators(t *testing.T) {
transports []string
}{
{"go", NewGolangGenerator(), "go", []string{"stdio", "rest", "websocket"}},
{"java", NewJavaGenerator(), "java", []string{"stdio"}},
{"javascript", NewNodeGenerator(), "javascript", []string{"stdio"}},
{"python", NewPythonGenerator(), "python", []string{"stdio"}},
{"java", NewJavaGenerator(), "java", []string{"stdio", "rest", "websocket"}},
{"javascript", NewNodeGenerator(), "javascript", []string{"stdio", "rest", "websocket"}},
{"python", NewPythonGenerator(), "python", []string{"stdio", "rest", "websocket"}},
}

for _, tt := range tests {
Expand Down
4 changes: 3 additions & 1 deletion internal/generators/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ func NewJavaGenerator() *JavaGenerator { return &JavaGenerator{} }

func (g *JavaGenerator) GetLanguage() string { return "java" }

func (g *JavaGenerator) GetSupportedTransports() []string { return []string{"stdio"} }
func (g *JavaGenerator) GetSupportedTransports() []string {
return []string{"stdio", "rest", "websocket"}
}

// Generate scaffolds a Java project using the provided configuration.
func (g *JavaGenerator) Generate(config *core.ProjectConfig) error {
Expand Down
2 changes: 1 addition & 1 deletion internal/generators/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (g *NodeGenerator) GetLanguage() string {

// GetSupportedTransports lists the supported transport mechanisms.
func (g *NodeGenerator) GetSupportedTransports() []string {
return []string{"stdio"}
return []string{"stdio", "rest", "websocket"}
}

// Generate scaffolds a Node.js project using the provided configuration.
Expand Down
4 changes: 3 additions & 1 deletion internal/generators/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ func NewPythonGenerator() *PythonGenerator { return &PythonGenerator{} }
func (g *PythonGenerator) GetLanguage() string { return "python" }

// GetSupportedTransports lists the transports supported by the generator.
func (g *PythonGenerator) GetSupportedTransports() []string { return []string{"stdio"} }
func (g *PythonGenerator) GetSupportedTransports() []string {
return []string{"stdio", "rest", "websocket"}
}

// Generate scaffolds a Python project using the provided configuration.
func (g *PythonGenerator) Generate(config *core.ProjectConfig) error {
Expand Down
40 changes: 40 additions & 0 deletions internal/generators/templates/go/http/cmd/server/main.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"

"github.com/gorilla/mux"
"{{.ModuleName}}/internal/handlers"
"{{.ModuleName}}/pkg/mcp"
)

func main() {
fmt.Fprintf(os.Stderr, "Starting {{.Config.Name}} MCP Server (http mode)...\n")

server := mcp.NewServer()
handler := handlers.NewHandler()

// Register handlers
server.RegisterResourceHandler(handler.HandleListResources)
server.RegisterResourceReadHandler(handler.HandleReadResource)
server.RegisterToolHandler(handler.HandleListTools)
server.RegisterCallToolHandler(handler.HandleCallTool)

router := mux.NewRouter()
router.HandleFunc("/mcp", func(w http.ResponseWriter, r *http.Request) {
var req mcp.Request
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
res := server.HandleRequest(req)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}).Methods(http.MethodPost)

log.Fatal(http.ListenAndServe(":8080", router))
}
1 change: 1 addition & 0 deletions internal/generators/templates/go/stdio/go.mod.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ go 1.22
require (
github.com/gorilla/mux v1.8.0
{{if eq .Config.Transport "rest"}}github.com/gorilla/handlers v1.5.1{{end}}
{{if eq .Config.Transport "websocket"}}github.com/gorilla/websocket v1.5.0{{end}}
)
58 changes: 58 additions & 0 deletions internal/generators/templates/go/websocket/cmd/server/main.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"

"github.com/gorilla/websocket"
"{{.ModuleName}}/internal/handlers"
"{{.ModuleName}}/pkg/mcp"
)

var upgrader = websocket.Upgrader{}

func main() {
fmt.Fprintf(os.Stderr, "Starting {{.Config.Name}} MCP Server (websocket mode)...\n")

server := mcp.NewServer()
handler := handlers.NewHandler()

// Register handlers
server.RegisterResourceHandler(handler.HandleListResources)
server.RegisterResourceReadHandler(handler.HandleReadResource)
server.RegisterToolHandler(handler.HandleListTools)
server.RegisterCallToolHandler(handler.HandleCallTool)

http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("upgrade error: %v", err)
return
}
defer conn.Close()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
log.Printf("read error: %v", err)
break
}
var req mcp.Request
if err := json.Unmarshal(msg, &req); err != nil {
log.Printf("json error: %v", err)
continue
}
res := server.HandleRequest(req)
resBytes, err := json.Marshal(res)
if err != nil {
log.Printf("json error: %v", err)
continue
}
conn.WriteMessage(websocket.TextMessage, resBytes)
}
})

log.Fatal(http.ListenAndServe(":8081", nil))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package {{.PackageName}};

import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.*;
import java.net.InetSocketAddress;
import org.json.JSONObject;
import {{.PackageName}}.handlers.MCPHandler;

public class Main {
public static void main(String[] args) throws Exception {
System.err.println("Starting {{.Config.Name}} MCP Server (http mode)...");
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/mcp", new HttpHandler() {
public void handle(HttpExchange ex) throws IOException {
if (!"POST".equals(ex.getRequestMethod())) {
ex.sendResponseHeaders(405, -1);
return;
}
String body = new String(ex.getRequestBody().readAllBytes());
try {
JSONObject req = new JSONObject(body);
JSONObject res = MCPHandler.handleRequest(req);
byte[] resp = res.toString().getBytes();
ex.getResponseHeaders().add("Content-Type", "application/json");
ex.sendResponseHeaders(200, resp.length);
ex.getResponseBody().write(resp);
} catch (Exception e) {
ex.sendResponseHeaders(400, -1);
} finally {
ex.close();
}
}
});
server.start();
}
}
7 changes: 7 additions & 0 deletions internal/generators/templates/java/stdio/pom.xml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,12 @@
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>
{{if eq .Config.Transport "websocket"}}
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.3</version>
</dependency>
{{end}}
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package {{.PackageName}};

import java.net.InetSocketAddress;
import org.java_websocket.server.WebSocketServer;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.json.JSONObject;
import {{.PackageName}}.handlers.MCPHandler;

public class Main extends WebSocketServer {
public Main() {
super(new InetSocketAddress(8081));
}

@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {}

@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {}

@Override
public void onMessage(WebSocket conn, String message) {
try {
JSONObject req = new JSONObject(message);
JSONObject res = MCPHandler.handleRequest(req);
conn.send(res.toString());
} catch (Exception e) {
System.err.println("Error processing message: " + e.getMessage());
}
}

@Override
public void onError(WebSocket conn, Exception ex) {
System.err.println("WebSocket error: " + ex.getMessage());
}

@Override
public void onStart() {
System.err.println("Starting {{.Config.Name}} MCP Server (websocket mode)...");
}

public static void main(String[] args) {
new Main().start();
}
}
29 changes: 29 additions & 0 deletions internal/generators/templates/node/http/src/index.js.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import http from 'http';
import { handleRequest } from './handlers/mcp.js';

console.error('Starting {{.Config.Name}} MCP Server (http mode)...');

const server = http.createServer((req, res) => {
if (req.method !== 'POST') {
res.statusCode = 405;
return res.end();
}
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
const reqObj = JSON.parse(body);
const result = handleRequest(reqObj);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(result));
} catch (err) {
console.error('Error handling request:', err.message);
res.statusCode = 400;
res.end();
}
});
});

server.listen(8080, () => {
console.error('HTTP server listening on 8080');
});
3 changes: 2 additions & 1 deletion internal/generators/templates/node/stdio/package.json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"start": "node src/index.js",
"test": "echo \"No test specified\" && exit 1",
"build": "echo \"No build step defined\" && exit 1"
}
},
"dependencies": { {{if eq .Config.Transport "websocket"}}"ws": "^8.13.0"{{end}} }
}
18 changes: 18 additions & 0 deletions internal/generators/templates/node/websocket/src/index.js.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { WebSocketServer } from 'ws';
import { handleRequest } from './handlers/mcp.js';

console.error('Starting {{.Config.Name}} MCP Server (websocket mode)...');

const wss = new WebSocketServer({ port: 8081 });

wss.on('connection', ws => {
ws.on('message', message => {
try {
const reqObj = JSON.parse(message);
const res = handleRequest(reqObj);
ws.send(JSON.stringify(res));
} catch (err) {
console.error('Error handling message:', err.message);
}
});
});
Loading