Textwell MCP Server
by worldnine
- tools
package tools
import (
"context"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/nguyenvanduocit/all-in-one-model-context-protocol/services"
"github.com/nguyenvanduocit/all-in-one-model-context-protocol/util"
"google.golang.org/api/calendar/v3"
"google.golang.org/api/option"
)
func RegisterCalendarTools(s *server.MCPServer) {
// Create event tool
createEventTool := mcp.NewTool("calendar_create_event",
mcp.WithDescription("Create a new event in Google Calendar"),
mcp.WithString("summary", mcp.Required(), mcp.Description("Title of the event")),
mcp.WithString("description", mcp.Description("Description of the event")),
mcp.WithString("start_time", mcp.Required(), mcp.Description("Start time of the event in RFC3339 format (e.g., 2023-12-25T09:00:00Z)")),
mcp.WithString("end_time", mcp.Required(), mcp.Description("End time of the event in RFC3339 format")),
mcp.WithString("attendees", mcp.Description("Comma-separated list of attendee email addresses")),
)
s.AddTool(createEventTool, util.ErrorGuard(calendarCreateEventHandler))
// List events tool
listEventsTool := mcp.NewTool("calendar_list_events",
mcp.WithDescription("List upcoming events in Google Calendar"),
mcp.WithString("time_min", mcp.Description("Start time for the search in RFC3339 format (default: now)")),
mcp.WithString("time_max", mcp.Description("End time for the search in RFC3339 format (default: 1 week from now)")),
mcp.WithNumber("max_results", mcp.Description("Maximum number of events to return (default: 10)")),
)
s.AddTool(listEventsTool, util.ErrorGuard(calendarListEventsHandler))
// Update event tool
updateEventTool := mcp.NewTool("calendar_update_event",
mcp.WithDescription("Update an existing event in Google Calendar"),
mcp.WithString("event_id", mcp.Required(), mcp.Description("ID of the event to update")),
mcp.WithString("summary", mcp.Description("New title of the event")),
mcp.WithString("description", mcp.Description("New description of the event")),
mcp.WithString("start_time", mcp.Description("New start time of the event in RFC3339 format")),
mcp.WithString("end_time", mcp.Description("New end time of the event in RFC3339 format")),
mcp.WithString("attendees", mcp.Description("Comma-separated list of new attendee email addresses")),
)
s.AddTool(updateEventTool, util.ErrorGuard(calendarUpdateEventHandler))
// Respond to event tool
respondToEventTool := mcp.NewTool("calendar_respond_to_event",
mcp.WithDescription("Respond to an event invitation in Google Calendar"),
mcp.WithString("event_id", mcp.Required(), mcp.Description("ID of the event to respond to")),
mcp.WithString("response", mcp.Required(), mcp.Description("Your response (accepted, declined, or tentative)")),
)
s.AddTool(respondToEventTool, util.ErrorGuard(calendarRespondToEventHandler))
}
var calendarService = sync.OnceValue[*calendar.Service](func() *calendar.Service {
ctx := context.Background()
tokenFile := os.Getenv("GOOGLE_TOKEN_FILE")
if tokenFile == "" {
panic("GOOGLE_TOKEN_FILE environment variable must be set")
}
credentialsFile := os.Getenv("GOOGLE_CREDENTIALS_FILE")
if credentialsFile == "" {
panic("GOOGLE_CREDENTIALS_FILE environment variable must be set")
}
client := services.GoogleHttpClient(tokenFile, credentialsFile)
srv, err := calendar.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
panic(fmt.Sprintf("failed to create Calendar service: %v", err))
}
return srv
})
func calendarCreateEventHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
summary, _ := arguments["summary"].(string)
description, _ := arguments["description"].(string)
startTimeStr, _ := arguments["start_time"].(string)
endTimeStr, _ := arguments["end_time"].(string)
attendeesStr, _ := arguments["attendees"].(string)
startTime, err := time.Parse(time.RFC3339, startTimeStr)
if err != nil {
return mcp.NewToolResultError("Invalid start_time format"), nil
}
endTime, err := time.Parse(time.RFC3339, endTimeStr)
if err != nil {
return mcp.NewToolResultError("Invalid end_time format"), nil
}
var attendees []*calendar.EventAttendee
if attendeesStr != "" {
for _, email := range strings.Split(attendeesStr, ",") {
attendees = append(attendees, &calendar.EventAttendee{Email: email})
}
}
event := &calendar.Event{
Summary: summary,
Description: description,
Start: &calendar.EventDateTime{
DateTime: startTime.Format(time.RFC3339),
},
End: &calendar.EventDateTime{
DateTime: endTime.Format(time.RFC3339),
},
Attendees: attendees,
}
createdEvent, err := calendarService().Events.Insert("primary", event).Do()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to create event: %v", err)), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Successfully created event with ID: %s", createdEvent.Id)), nil
}
func calendarListEventsHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
timeMinStr, ok := arguments["time_min"].(string)
if !ok || timeMinStr == "" {
timeMinStr = time.Now().Format(time.RFC3339)
}
timeMaxStr, ok := arguments["time_max"].(string)
if !ok || timeMaxStr == "" {
timeMaxStr = time.Now().AddDate(0, 0, 7).Format(time.RFC3339) // 1 week from now
}
maxResults, ok := arguments["max_results"].(float64)
if !ok {
maxResults = 10
}
events, err := calendarService().Events.List("primary").
ShowDeleted(false).
SingleEvents(true).
TimeMin(timeMinStr).
TimeMax(timeMaxStr).
MaxResults(int64(maxResults)).
OrderBy("startTime").
Do()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to list events: %v", err)), nil
}
var result strings.Builder
result.WriteString(fmt.Sprintf("Found %d upcoming events:\n\n", len(events.Items)))
for _, item := range events.Items {
start, _ := time.Parse(time.RFC3339, item.Start.DateTime)
end, _ := time.Parse(time.RFC3339, item.End.DateTime)
result.WriteString(fmt.Sprintf("Event: %s\n", item.Summary))
result.WriteString(fmt.Sprintf("Start: %s\n", start.Format("2006-01-02 15:04")))
result.WriteString(fmt.Sprintf("End: %s\n", end.Format("2006-01-02 15:04")))
if item.Description != "" {
result.WriteString(fmt.Sprintf("Description: %s\n", item.Description))
}
result.WriteString("-------------------\n")
}
return mcp.NewToolResultText(result.String()), nil
}
func calendarUpdateEventHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
eventID, _ := arguments["event_id"].(string)
summary, _ := arguments["summary"].(string)
description, _ := arguments["description"].(string)
startTimeStr, _ := arguments["start_time"].(string)
endTimeStr, _ := arguments["end_time"].(string)
attendeesStr, _ := arguments["attendees"].(string)
event, err := calendarService().Events.Get("primary", eventID).Do()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to get event: %v", err)), nil
}
if summary != "" {
event.Summary = summary
}
if description != "" {
event.Description = description
}
if startTimeStr != "" {
startTime, err := time.Parse(time.RFC3339, startTimeStr)
if err != nil {
return mcp.NewToolResultError("Invalid start_time format"), nil
}
event.Start.DateTime = startTime.Format(time.RFC3339)
}
if endTimeStr != "" {
endTime, err := time.Parse(time.RFC3339, endTimeStr)
if err != nil {
return mcp.NewToolResultError("Invalid end_time format"), nil
}
event.End.DateTime = endTime.Format(time.RFC3339)
}
if attendeesStr != "" {
var attendees []*calendar.EventAttendee
for _, email := range strings.Split(attendeesStr, ",") {
attendees = append(attendees, &calendar.EventAttendee{Email: email})
}
event.Attendees = attendees
}
updatedEvent, err := calendarService().Events.Update("primary", eventID, event).Do()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to update event: %v", err)), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Successfully updated event with ID: %s", updatedEvent.Id)), nil
}
func calendarRespondToEventHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
eventID, _ := arguments["event_id"].(string)
response, _ := arguments["response"].(string)
event, err := calendarService().Events.Get("primary", eventID).Do()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to get event: %v", err)), nil
}
for _, attendee := range event.Attendees {
if attendee.Self {
attendee.ResponseStatus = response
break
}
}
_, err = calendarService().Events.Update("primary", eventID, event).Do()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to update event response: %v", err)), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Successfully responded '%s' to event with ID: %s", response, eventID)), nil
}