Files
vikunja-mcp/main.go

109 lines
2.6 KiB
Go

// Vikunja MCP - Developed by acidvegas in Go (https://git.acid.vegas)
// vikunja-mcp/main.go
package main
import (
"context"
_ "embed"
"flag"
"fmt"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
//go:embed instructions.txt
var instructions string
type config struct {
baseURL string
token string
httpClient *http.Client
}
func main() {
log.SetFlags(0)
log.SetPrefix("vikunja-mcp: ")
vikunjaURL := strings.TrimRight(os.Getenv("VIKUNJA_URL"), "/")
if vikunjaURL == "" {
log.Fatal("VIKUNJA_URL is not set")
}
token := os.Getenv("VIKUNJA_TOKEN")
if token == "" {
log.Fatal("VIKUNJA_TOKEN is not set")
}
defaultTransport := envOr("VIKUNJA_MCP_TRANSPORT", "stdio")
defaultHost := envOr("VIKUNJA_MCP_HOST", "127.0.0.1")
defaultPort := 8000
if v := os.Getenv("VIKUNJA_MCP_PORT"); v != "" {
if n, err := strconv.Atoi(v); err == nil {
defaultPort = n
}
}
var transport, host string
var port int
flag.StringVar(&transport, "transport", defaultTransport, "Transport: stdio or http")
flag.StringVar(&transport, "t", defaultTransport, "Transport (shorthand)")
flag.StringVar(&host, "host", defaultHost, "Host for http transport")
flag.IntVar(&port, "port", defaultPort, "Port for http transport")
flag.Parse()
cfg := &config{
baseURL: vikunjaURL + "/api/v1",
token: token,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
spec, err := loadSpec(cfg.baseURL + "/docs.json")
if err != nil {
log.Fatalf("spec load failed: %v", err)
}
patchSpec(spec)
server := mcp.NewServer(
&mcp.Implementation{Name: "vikunja", Version: "1.0.0"},
&mcp.ServerOptions{Instructions: instructions},
)
n := registerTools(server, cfg, spec)
log.Printf("loaded %d tools from vikunja spec", n)
switch strings.ToLower(transport) {
case "stdio":
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatal(err)
}
case "http":
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return server }, nil)
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, _ = w.Write([]byte("ok"))
})
mux.Handle("/", handler)
addr := fmt.Sprintf("%s:%d", host, port)
log.Printf("http listening on %s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Fatal(err)
}
default:
log.Fatalf("unknown transport: %s (use stdio or http)", transport)
}
}
func envOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}