mirror of
https://github.com/taigrr/gotify-mcp.git
synced 2025-10-23 01:27:18 +03:00
initial
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
main
|
||||
gotify-mcp
|
||||
.crush/
|
||||
93
README.md
Normal file
93
README.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Gotify MCP Server
|
||||
|
||||
A Model Context Protocol (MCP) server that enables LLMs to send notifications to a Gotify server. This allows AI assistants to notify users about task completion, request help, or provide activity summaries.
|
||||
|
||||
## Features
|
||||
|
||||
- **Send Message**: Send custom messages with configurable priority and title
|
||||
- **Ask for Help**: Send help request notifications with context and error details
|
||||
- **Notify Completion**: Send task completion notifications with results
|
||||
- **Summarize Activity**: Send activity summaries with optional details
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The MCP server requires the following environment variables:
|
||||
|
||||
- `GOTIFY_URL`: The URL of your Gotify server (e.g., `https://gotify.example.com`)
|
||||
- `GOTIFY_TOKEN`: Your Gotify application token for authentication
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/taigrr/gotify-mcp.git
|
||||
cd gotify-mcp
|
||||
```
|
||||
|
||||
2. Build the binary:
|
||||
```bash
|
||||
go build -o gotify-mcp
|
||||
```
|
||||
|
||||
3. Set up environment variables:
|
||||
```bash
|
||||
export GOTIFY_URL="https://your-gotify-server.com"
|
||||
export GOTIFY_TOKEN="your-application-token"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The MCP server communicates over stdio and provides the following tools:
|
||||
|
||||
### send-message
|
||||
Send a custom message to Gotify.
|
||||
|
||||
**Parameters:**
|
||||
- `message` (required): The message content to send
|
||||
- `title` (optional): Title for the message
|
||||
- `priority` (optional): Message priority (0-10, default: 5)
|
||||
|
||||
### ask-for-help
|
||||
Send a help request notification.
|
||||
|
||||
**Parameters:**
|
||||
- `context` (required): Context or description of what help is needed
|
||||
- `error` (optional): Optional error message or details
|
||||
|
||||
### notify-completion
|
||||
Send a task completion notification.
|
||||
|
||||
**Parameters:**
|
||||
- `task` (required): Description of the completed task
|
||||
- `result` (optional): Optional result or outcome details
|
||||
|
||||
### summarize-activity
|
||||
Send an activity summary notification.
|
||||
|
||||
**Parameters:**
|
||||
- `summary` (required): Summary of activities or current status
|
||||
- `details` (optional): Optional additional details
|
||||
|
||||
## Integration with MCP Clients
|
||||
|
||||
To use this MCP server with an MCP client, configure it to run the `gotify-mcp` binary with the appropriate environment variables set.
|
||||
|
||||
Example configuration for Claude Desktop:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"gotify": {
|
||||
"command": "/path/to/gotify-mcp",
|
||||
"env": {
|
||||
"GOTIFY_URL": "https://your-gotify-server.com",
|
||||
"GOTIFY_TOKEN": "your-application-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
11
go.mod
Normal file
11
go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module github.com/taigrr/gotify-mcp
|
||||
|
||||
go 1.24.5
|
||||
|
||||
require github.com/mark3labs/mcp-go v0.33.0
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
)
|
||||
26
go.sum
Normal file
26
go.sum
Normal file
@@ -0,0 +1,26 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mark3labs/mcp-go v0.33.0 h1:naxhjnTIs/tyPZmWUZFuG0lDmdA6sUyYGGf3gsHvTCc=
|
||||
github.com/mark3labs/mcp-go v0.33.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
247
main.go
Normal file
247
main.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
type GotifyMessage struct {
|
||||
Message string `json:"message"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
}
|
||||
|
||||
func getStringArg(args map[string]interface{}, key string, defaultValue string) string {
|
||||
if val, ok := args[key].(string); ok {
|
||||
return val
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getNumberArg(args map[string]interface{}, key string, defaultValue float64) float64 {
|
||||
if val, ok := args[key].(float64); ok {
|
||||
return val
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := server.NewMCPServer(
|
||||
"Gotify Notification Server",
|
||||
"1.0.0",
|
||||
server.WithToolCapabilities(false),
|
||||
)
|
||||
|
||||
sendMessageTool := mcp.NewTool("send-message",
|
||||
mcp.WithDescription("Send a message to a Gotify server for notifications"),
|
||||
mcp.WithString("message",
|
||||
mcp.Required(),
|
||||
mcp.Description("The message content to send"),
|
||||
),
|
||||
mcp.WithString("title",
|
||||
mcp.Description("Optional title for the message"),
|
||||
),
|
||||
mcp.WithNumber("priority",
|
||||
mcp.Description("Message priority (0-10, default: 5)"),
|
||||
),
|
||||
)
|
||||
|
||||
askForHelpTool := mcp.NewTool("ask-for-help",
|
||||
mcp.WithDescription("Send a help request notification to the user via Gotify"),
|
||||
mcp.WithString("context",
|
||||
mcp.Required(),
|
||||
mcp.Description("Context or description of what help is needed"),
|
||||
),
|
||||
mcp.WithString("error",
|
||||
mcp.Description("Optional error message or details"),
|
||||
),
|
||||
)
|
||||
|
||||
notifyCompletionTool := mcp.NewTool("notify-completion",
|
||||
mcp.WithDescription("Send a completion notification to the user via Gotify"),
|
||||
mcp.WithString("task",
|
||||
mcp.Required(),
|
||||
mcp.Description("Description of the completed task"),
|
||||
),
|
||||
mcp.WithString("result",
|
||||
mcp.Description("Optional result or outcome details"),
|
||||
),
|
||||
)
|
||||
|
||||
summarizeTool := mcp.NewTool("summarize-activity",
|
||||
mcp.WithDescription("Send a summary of current activities or status to the user via Gotify"),
|
||||
mcp.WithString("summary",
|
||||
mcp.Required(),
|
||||
mcp.Description("Summary of activities or current status"),
|
||||
),
|
||||
mcp.WithString("details",
|
||||
mcp.Description("Optional additional details"),
|
||||
),
|
||||
)
|
||||
|
||||
s.AddTool(sendMessageTool, sendMessage)
|
||||
s.AddTool(askForHelpTool, askForHelp)
|
||||
s.AddTool(notifyCompletionTool, notifyCompletion)
|
||||
s.AddTool(summarizeTool, summarizeActivity)
|
||||
|
||||
if err := server.ServeStdio(s); err != nil {
|
||||
fmt.Printf("Server error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func sendGotifyMessage(message GotifyMessage) error {
|
||||
gotifyURL := os.Getenv("GOTIFY_URL")
|
||||
gotifyToken := os.Getenv("GOTIFY_TOKEN")
|
||||
|
||||
if gotifyURL == "" {
|
||||
return fmt.Errorf("GOTIFY_URL environment variable is not set")
|
||||
}
|
||||
if gotifyToken == "" {
|
||||
return fmt.Errorf("GOTIFY_TOKEN environment variable is not set")
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal message: %w", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/message?token=%s", gotifyURL, gotifyToken)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send message: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("gotify server returned status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendMessage(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
message, err := request.RequireString("message")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
|
||||
args, ok := request.Params.Arguments.(map[string]interface{})
|
||||
if !ok {
|
||||
return mcp.NewToolResultError("invalid arguments type"), nil
|
||||
}
|
||||
|
||||
title := getStringArg(args, "title", "")
|
||||
priority := getNumberArg(args, "priority", 5)
|
||||
|
||||
gotifyMsg := GotifyMessage{
|
||||
Message: message,
|
||||
Title: title,
|
||||
Priority: int(priority),
|
||||
}
|
||||
|
||||
if err := sendGotifyMessage(gotifyMsg); err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to send message: %s", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Message sent successfully"), nil
|
||||
}
|
||||
|
||||
func askForHelp(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
contextStr, err := request.RequireString("context")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
|
||||
args, ok := request.Params.Arguments.(map[string]interface{})
|
||||
if !ok {
|
||||
return mcp.NewToolResultError("invalid arguments type"), nil
|
||||
}
|
||||
|
||||
errorMsg := getStringArg(args, "error", "")
|
||||
|
||||
message := fmt.Sprintf("🆘 Help needed: %s", contextStr)
|
||||
if errorMsg != "" {
|
||||
message += fmt.Sprintf("\nError: %s", errorMsg)
|
||||
}
|
||||
|
||||
gotifyMsg := GotifyMessage{
|
||||
Message: message,
|
||||
Title: "Help Request",
|
||||
Priority: 8,
|
||||
}
|
||||
|
||||
if err := sendGotifyMessage(gotifyMsg); err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to send help request: %s", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Help request sent successfully"), nil
|
||||
}
|
||||
|
||||
func notifyCompletion(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
task, err := request.RequireString("task")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
|
||||
args, ok := request.Params.Arguments.(map[string]interface{})
|
||||
if !ok {
|
||||
return mcp.NewToolResultError("invalid arguments type"), nil
|
||||
}
|
||||
|
||||
result := getStringArg(args, "result", "")
|
||||
|
||||
message := fmt.Sprintf("✅ Task completed: %s", task)
|
||||
if result != "" {
|
||||
message += fmt.Sprintf("\nResult: %s", result)
|
||||
}
|
||||
|
||||
gotifyMsg := GotifyMessage{
|
||||
Message: message,
|
||||
Title: "Task Completed",
|
||||
Priority: 6,
|
||||
}
|
||||
|
||||
if err := sendGotifyMessage(gotifyMsg); err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to send completion notification: %s", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Completion notification sent successfully"), nil
|
||||
}
|
||||
|
||||
func summarizeActivity(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
summary, err := request.RequireString("summary")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
|
||||
args, ok := request.Params.Arguments.(map[string]interface{})
|
||||
if !ok {
|
||||
return mcp.NewToolResultError("invalid arguments type"), nil
|
||||
}
|
||||
|
||||
details := getStringArg(args, "details", "")
|
||||
|
||||
message := fmt.Sprintf("📊 Activity Summary: %s", summary)
|
||||
if details != "" {
|
||||
message += fmt.Sprintf("\nDetails: %s", details)
|
||||
}
|
||||
|
||||
gotifyMsg := GotifyMessage{
|
||||
Message: message,
|
||||
Title: "Activity Summary",
|
||||
Priority: 4,
|
||||
}
|
||||
|
||||
if err := sendGotifyMessage(gotifyMsg); err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to send summary: %s", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Activity summary sent successfully"), nil
|
||||
}
|
||||
Reference in New Issue
Block a user