Commit 39c226b
Changed files (1)
DESIGN.md
@@ -1,4 +1,4 @@
-## ๐ MCP Design Document: Go Implementation
+# MCP Design Document: Go Implementation
**Project Name:** `mcp`
**Target Repository:** `https://github.com/xlgmokha/mcp`
@@ -7,7 +7,7 @@
---
-### ๐งฑ Overall Project Structure
+## ๐ Overall Project Structure
```
mcp/
@@ -20,52 +20,50 @@ mcp/
โ โโโ mcp/ # Shared MCP protocol tooling (e.g., request/response structs)
โโโ cmd/
โโโ git/
- โ โโโ main.go # Git server
+ โ โโโ main.go
+ โ โโโ handler.go
+ โ โโโ gitutils.go
โโโ <future-server>/
- โ โโโ main.go # Placeholder for future mcp servers
+ โ โโโ main.go
```
---
-### ๐ Phase 1: Implementing `cmd/git`
+## ๐ Phase 1: Implementing `cmd/git`
-#### Objective
+### Objective
Replicate the functionality of the [Python git server](https://github.com/modelcontextprotocol/servers/tree/main/src/git) from the MCP project in Go.
-#### Features
+### Features
-The Go server must:
+- Accept stdin JSON requests conforming to the MCP protocol.
+- Supported commands:
+ - `clone`: Clone a remote Git repository.
+ - `list`: List files in the repo (default to `HEAD`).
+ - `read`: Read file contents at specific paths.
+ - `head`: Show latest commit hash.
+- Return responses as JSON to stdout.
+- Print logs and errors to stderr.
+- Work as a CLI tool or JSON-RPC-compatible stdin/stdout process.
-1. Accept stdin JSON requests conforming to the MCP protocol (e.g. `{"action": "list", "repo": "https://github.com/..."} `).
-2. Support these commands:
+### External Dependencies
- * `clone`: Clone a remote Git repository.
- * `list`: List files in the repo (default to `HEAD`).
- * `read`: Read file contents at specific paths.
- * `head`: Show latest commit hash.
-3. Return responses as JSON to stdout.
-4. Print logs and errors to stderr.
-5. Be able to work as a CLI tool or JSON-RPC-compatible stdin/stdout process.
+- `go-git` ([https://github.com/go-git/go-git](https://github.com/go-git/go-git)) for Git operations.
+- Optional: `spf13/cobra` if CLI argument parsing is needed in the future.
-#### External Dependencies
-
-* `go-git` ([https://github.com/go-git/go-git](https://github.com/go-git/go-git)) for Git operations.
-* Optional: `spf13/cobra` if command-line execution is needed in the future.
-
-#### Internal Structure for Git Server
+### Internal Structure for Git Server
```
cmd/git/
-โโโ main.go
-โโโ handler.go # MCP request router
-โโโ gitutils.go # Git operations: clone, read, list, head
+โโโ main.go # Entry point
+โโโ handler.go # Routes incoming MCP requests
+โโโ gitutils.go # Handles Git-specific operations
```
-#### JSON Message Format
-
-Request (stdin):
+### JSON Message Format
+#### Request (stdin)
```json
{
"action": "list",
@@ -74,17 +72,15 @@ Request (stdin):
}
```
-Response (stdout):
-
+#### Response (stdout)
```json
{
"status": "ok",
- "files": ["README.md", "src/git/main.py", ...]
+ "files": ["README.md", "src/git/main.py"]
}
```
-On error:
-
+#### Error (stdout)
```json
{
"status": "error",
@@ -94,34 +90,197 @@ On error:
---
-### ๐ง Design Principles
+## ๐ง Design Principles
-* **Modular design:** Each server is a self-contained CLI app under `cmd/`.
-* **Streaming stdin/stdout:** Allow compatibility with tools like OpenAI functions or Claude tool use.
-* **Lightweight and efficient:** Avoid unnecessary dependencies.
-* **Testable:** Extract logic from `main.go` to make unit testing easier.
+- **Modular Design**: Each server lives in `cmd/` as an isolated command.
+- **Stream IO**: Communicate via stdin/stdout with JSON.
+- **Minimal Dependencies**: Use Go standard library and `go-git`.
+- **Testable**: Extract core logic into testable units.
---
-### ๐ฃ๏ธ Future Work (beyond git)
+## ๐ฎ Future Commands
-* `cmd/fs/main.go`: Local filesystem server.
-* `cmd/bash/main.go`: Command-line interpreter over MCP.
-* `cmd/sql/main.go`: SQL query server.
-* `cmd/http/main.go`: Interact with HTTP APIs.
-* `cmd/gpt/main.go`: Call local or remote LLMs.
-* `cmd/py/main.go`: Execute Python snippets.
+- `cmd/fs/main.go`: Local filesystem server.
+- `cmd/bash/main.go`: Bash command interpreter.
+- `cmd/sql/main.go`: SQL query server.
+- `cmd/http/main.go`: HTTP API interaction.
+- `cmd/gpt/main.go`: LLM invocation.
+- `cmd/py/main.go`: Python code runner.
-Each command can follow the same pattern: read JSON input from stdin, process, write JSON to stdout.
+---
+
+## โ
Acceptance Criteria
+
+- `go run cmd/git/main.go` processes JSON stdin and performs Git tasks correctly.
+- Errors are written to stderr and returned as JSON.
+- Repo contains:
+ - `README.md` with instructions.
+ - Example JSON inputs/outputs.
+ - Template to add new MCP command servers under `cmd/`.
---
-### โ
Acceptance Criteria
+## ๐ Bootstrap Code: Go MCP Git Server
+
+### `cmd/git/main.go`
+```go
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "mcp/cmd/git"
+)
-* Running `go run cmd/git/main.go` with valid input via stdin performs the Git actions as expected.
-* Fails gracefully with meaningful stderr logs and proper JSON error responses.
-* Repository includes:
+func main() {
+ var req git.Request
+ decoder := json.NewDecoder(os.Stdin)
+ encoder := json.NewEncoder(os.Stdout)
+
+ if err := decoder.Decode(&req); err != nil {
+ fmt.Fprintf(os.Stderr, "decode error: %v\n", err)
+ encoder.Encode(git.ErrorResponse("invalid JSON input"))
+ return
+ }
+
+ resp := git.HandleRequest(req)
+ encoder.Encode(resp)
+}
+```
+
+### `cmd/git/handler.go`
+```go
+package git
+
+type Request struct {
+ Action string `json:"action"`
+ Repo string `json:"repo"`
+ Path string `json:"path,omitempty"`
+ Ref string `json:"ref,omitempty"`
+}
+
+type Response map[string]interface{}
+
+func ErrorResponse(msg string) Response {
+ return Response{"status": "error", "error": msg}
+}
- * `README.md` with usage instructions.
- * Example inputs/outputs for testing.
- * Clear instructions to add more commands under `cmd/`.
+func OkResponse(data map[string]interface{}) Response {
+ data["status"] = "ok"
+ return data
+}
+
+func HandleRequest(req Request) Response {
+ switch req.Action {
+ case "clone":
+ return handleClone(req)
+ case "list":
+ return handleList(req)
+ case "read":
+ return handleRead(req)
+ case "head":
+ return handleHead(req)
+ default:
+ return ErrorResponse("unsupported action")
+ }
+}
+```
+
+### `cmd/git/gitutils.go`
+```go
+package git
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ gitlib "github.com/go-git/go-git/v5"
+)
+
+func cloneRepo(url string) (string, error) {
+ dir, err := ioutil.TempDir("", "mcp-git-")
+ if err != nil {
+ return "", err
+ }
+
+ _, err = gitlib.PlainClone(dir, false, &gitlib.CloneOptions{
+ URL: url,
+ Progress: os.Stderr,
+ })
+ return dir, err
+}
+
+func handleClone(req Request) Response {
+ dir, err := cloneRepo(req.Repo)
+ if err != nil {
+ return ErrorResponse(fmt.Sprintf("clone failed: %v", err))
+ }
+ return OkResponse(map[string]interface{}{ "path": dir })
+}
+
+func handleList(req Request) Response {
+ dir, err := cloneRepo(req.Repo)
+ if err != nil {
+ return ErrorResponse(fmt.Sprintf("clone failed: %v", err))
+ }
+ defer os.RemoveAll(dir)
+
+ var files []string
+ err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ rel, _ := filepath.Rel(dir, path)
+ files = append(files, rel)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return ErrorResponse(fmt.Sprintf("walk failed: %v", err))
+ }
+
+ return OkResponse(map[string]interface{}{ "files": files })
+}
+
+func handleRead(req Request) Response {
+ dir, err := cloneRepo(req.Repo)
+ if err != nil {
+ return ErrorResponse(fmt.Sprintf("clone failed: %v", err))
+ }
+ defer os.RemoveAll(dir)
+
+ content, err := os.ReadFile(filepath.Join(dir, req.Path))
+ if err != nil {
+ return ErrorResponse(fmt.Sprintf("read failed: %v", err))
+ }
+
+ return OkResponse(map[string]interface{}{ "path": req.Path, "content": string(content) })
+}
+
+func handleHead(req Request) Response {
+ dir, err := cloneRepo(req.Repo)
+ if err != nil {
+ return ErrorResponse(fmt.Sprintf("clone failed: %v", err))
+ }
+ defer os.RemoveAll(dir)
+
+ r, err := gitlib.PlainOpen(dir)
+ if err != nil {
+ return ErrorResponse(fmt.Sprintf("open failed: %v", err))
+ }
+
+ h, err := r.Head()
+ if err != nil {
+ return ErrorResponse(fmt.Sprintf("head failed: %v", err))
+ }
+
+ return OkResponse(map[string]interface{}{ "ref": h.Name().String(), "hash": h.Hash().String() })
+}
+```