feat: 🎉 Setup simple mcp server
Tim Izzo tim@5ika.ch
Tue, 23 Dec 2025 13:49:36 +0100
6 files changed,
207 insertions(+),
0 deletions(-)
A
go.mod
@@ -0,0 +1,13 @@
+module 5ika/mcp-todo.txt + +go 1.25.5 + +require github.com/1set/todotxt v0.0.4 + +require ( + github.com/1set/gut v0.0.0-20201117175203-a82363231997 // indirect + github.com/google/jsonschema-go v0.3.0 // indirect + github.com/modelcontextprotocol/go-sdk v1.2.0 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + golang.org/x/oauth2 v0.30.0 // indirect +)
A
go.sum
@@ -0,0 +1,12 @@
+github.com/1set/gut v0.0.0-20201117175203-a82363231997 h1:za2jSkE1Rx56hTzBko3ZZ4gA/nq+rA/jVovWuAF4jyo= +github.com/1set/gut v0.0.0-20201117175203-a82363231997/go.mod h1:DpCCAL0dgBMQdiqPUIIRpdU9zNcIZwJjW+L/8Mb30mw= +github.com/1set/todotxt v0.0.4 h1:A8DpMwGxctq7oT3s/2uC26RkLMKHRnG1E7x/cD8IeSI= +github.com/1set/todotxt v0.0.4/go.mod h1:FILY0dppcGIZzNbr4WUZ1d2bd1YWTCllmGIuXt3MP9Y= +github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q= +github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s= +github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
A
logging.go
@@ -0,0 +1,53 @@
+// Copyright 2025 The Go MCP SDK Authors. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package main + +import ( + "context" + "log" + "time" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// createLoggingMiddleware creates an MCP middleware that logs method calls. +func createLoggingMiddleware() mcp.Middleware { + return func(next mcp.MethodHandler) mcp.MethodHandler { + return func( + ctx context.Context, + method string, + req mcp.Request, + ) (mcp.Result, error) { + start := time.Now() + sessionID := req.GetSession().ID() + + // Log request details. + log.Printf("[REQUEST] Session: %s | Method: %s", + sessionID, + method) + + // Call the actual handler. + result, err := next(ctx, method, req) + + // Log response details. + duration := time.Since(start) + + if err != nil { + log.Printf("[RESPONSE] Session: %s | Method: %s | Status: ERROR | Duration: %v | Error: %v", + sessionID, + method, + duration, + err) + } else { + log.Printf("[RESPONSE] Session: %s | Method: %s | Status: OK | Duration: %v", + sessionID, + method, + duration) + } + + return result, err + } + } +}
A
main.go
@@ -0,0 +1,51 @@
+package main + +import ( + "flag" + "fmt" + "log" + "net/http" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +var ( + host = flag.String("host", "localhost", "host to connect to/listen on") + port = flag.Int("port", 8080, "port number to connect to/listen on") + todoFilePath = flag.String("file", "./todo.txt", "path to todo.txt") +) + +func main() { + flag.Parse() + url := fmt.Sprintf("%s:%d", *host, *port) + + server := mcp.NewServer(&mcp.Implementation{ + Name: "5ika-mpc-todo.txt", + Version: "1.0.0", + }, nil) + + server.AddReceivingMiddleware(createLoggingMiddleware()) + + mcp.AddTool(server, &mcp.Tool{ + Name: "listTasks", + Description: "List tasks in todo.txt file", + }, getTasks) + mcp.AddTool(server, &mcp.Tool{ + Name: "addTask", + Description: "Add new task in todo.txt file", + }, addTask) + mcp.AddTool(server, &mcp.Tool{ + Name: "checkTask", + Description: "Set task as completed, done or finished in todo.txt file", + }, checkTask) + + handler := mcp.NewStreamableHTTPHandler(func(req *http.Request) *mcp.Server { + return server + }, nil) + + log.Printf("MCP server listening on %s", url) + + if err := http.ListenAndServe(url, handler); err != nil { + log.Fatalf("Server failed: %v", err) + } +}
A
tools.go
@@ -0,0 +1,77 @@
+package main + +import ( + "context" + "log" + + "github.com/modelcontextprotocol/go-sdk/mcp" + + todo "github.com/1set/todotxt" +) + +// var todoFilePath string = "./todo.txt" + +func getTasksList(path string) todo.TaskList { + if tasklist, err := todo.LoadFromPath(path); err != nil { + log.Fatal(err) + return todo.NewTaskList() + } else { + return tasklist + } +} + +type GetTasksListPrams struct { +} + +func getTasks(ctx context.Context, req *mcp.CallToolRequest, params *GetTasksListPrams) (*mcp.CallToolResult, any, error) { + tasksList := getTasksList(*todoFilePath) + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: tasksList.String()}, + }, + }, nil, nil +} + +type AddTaskParams struct { + TaskContent string `json:"taskContent"` +} + +func addTask(ctx context.Context, req *mcp.CallToolRequest, params *AddTaskParams) (*mcp.CallToolResult, any, error) { + tasksList := getTasksList(*todoFilePath) + newTask, err := todo.ParseTask(params.TaskContent) + + if err != nil { + log.Printf("Error on parsing task %s", params.TaskContent) + + return &mcp.CallToolResult{ + IsError: true, + }, nil, nil + } else { + tasksList.AddTask(newTask) + tasksList.WriteToPath(*todoFilePath) + log.Printf("New task added: %s", newTask.String()) + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "New tasks added"}, + }, + }, nil, nil + } +} + +type CheckTaskParams struct { + TaskIndex int `json:"taskIndex"` +} + +func checkTask(ctx context.Context, req *mcp.CallToolRequest, params *CheckTaskParams) (*mcp.CallToolResult, any, error) { + tasksList := getTasksList(*todoFilePath) + tasksList[params.TaskIndex].Complete() + tasksList.WriteToPath(*todoFilePath) + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "Task checked"}, + }, + }, nil, nil +}