# Directory Structure ``` ├── .gitignore ├── cmd │ └── server │ └── main.go ├── go.mod ├── go.sum ├── internal │ ├── app │ │ └── server.go │ ├── handlers │ │ ├── contact.go │ │ ├── http.go │ │ └── signature.go │ └── tools │ ├── contact.go │ └── signature.go ├── LICENSE └── README.md ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool 12 | *.out 13 | 14 | # Dependency directories 15 | vendor/ 16 | 17 | # Go workspace file 18 | go.work 19 | 20 | # IDE specific files 21 | .idea/ 22 | .vscode/ 23 | *.swp 24 | *.swo 25 | .DS_Store 26 | 27 | # Environment files 28 | .env 29 | .env.local 30 | .env.*.local 31 | 32 | # Log files 33 | *.log 34 | 35 | # Binary output directory 36 | bin/ 37 | dist/ ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Signaturit MCP ✍️ 2 | 3 | > **Note:** This is an unofficial integration project and is not affiliated with, officially maintained, or endorsed by Signaturit. 4 | 5 | This project is a demonstration of an MCP (Microservice Communication Protocol) server that integrates with Signaturit tools through their [public API](https://www.signaturit.com/api). It provides various functionalities for managing signature requests, including listing, creating, and handling signatures. 6 | 7 | ## Capabilities 🚀 8 | 9 | The MCP server provides the following tools to interact with Signaturit: 10 | 11 | - **get_signature** 📄: Retrieve details of a specific signature request using its ID 12 | - **create_signature** ✨: Create new signature requests using templates 13 | - Support for multiple signers 👥 14 | - Email or SMS delivery 📧 📱 15 | - Customizable expiration time ⏰ 16 | - Sequential or parallel signing workflow ⛓️ 17 | - Custom email/SMS messages 💬 18 | - Webhook integration for real-time notifications 🔔 19 | - **send_signature_reminder** 📬: Send reminder notifications to pending signers 20 | - **cancel_signature** ❌: Cancel active signature requests with optional reason 21 | 22 | ## Project Structure 📁 23 | 24 | - **cmd/server/main.go** 🎯: Entry point of the application. It initializes and starts the MCP server. 25 | - **internal/app/server.go** ⚙️: Contains the logic for creating and configuring the MCP server, including registering signature tools and handlers. 26 | - **internal/handlers/signature.go** 🛠️: Implements handler functions for various signature operations such as listing, retrieving, and managing signatures. 27 | - **internal/tools/signature.go** 🔧: Registers signature-related tools with the MCP server. 28 | 29 | ## Configuration ⚙️ 30 | 31 | ### API Authentication 🔐 32 | 33 | This server integrates with the Signaturit API and requires an API key for authentication. You need to: 34 | 35 | 1. Create an account at [Signaturit](https://www.signaturit.com) 36 | 2. Get your API key from the Signaturit dashboard 37 | 3. Set the API key as an environment variable: 38 | 39 | ```bash 40 | export SIGNATURIT_SECRET_TOKEN='your_api_key_here' 41 | ``` 42 | 43 | ## Prerequisites 📋 44 | 45 | 1. **Go Installation** 46 | - Go 1.16 or higher 47 | - Verify your installation: 48 | ```bash 49 | go version 50 | ``` 51 | 52 | 2. **Signaturit Account** 53 | - Active account at [Signaturit](https://www.signaturit.com) 54 | - Valid API key from the Signaturit dashboard 55 | 56 | ## Build 🔨 57 | 58 | 1. **Clone the repository** 59 | ```bash 60 | git clone https://github.com/yourusername/signaturtit_mcp.git 61 | cd signaturtit_mcp 62 | ``` 63 | 64 | 2. **Install dependencies** 65 | ```bash 66 | go mod download 67 | ``` 68 | 69 | 3. **Build the application** 70 | ```bash 71 | # Build for your current platform 72 | go build -o bin/signaturtit_mcp cmd/server/main.go 73 | 74 | # Build for specific platform (e.g., Linux) 75 | GOOS=linux GOARCH=amd64 go build -o bin/signaturtit_mcp cmd/server/main.go 76 | ``` 77 | 78 | 4. **Run the built binary** 79 | ```bash 80 | # Make sure you have set the required environment variables first 81 | export SIGNATURIT_SECRET_TOKEN='your_api_key_here' 82 | 83 | # Run the application 84 | ./bin/signaturtit_mcp 85 | ``` 86 | 87 | ## License 📜 88 | 89 | ``` 90 | Copyright 2024 Jordi Martin 91 | 92 | Licensed under the Apache License, Version 2.0 (the "License"); 93 | you may not use this file except in compliance with the License. 94 | You may obtain a copy of the License at 95 | 96 | http://www.apache.org/licenses/LICENSE-2.0 97 | 98 | Unless required by applicable law or agreed to in writing, software 99 | distributed under the License is distributed on an "AS IS" BASIS, 100 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 101 | See the License for the specific language governing permissions and 102 | limitations under the License. 103 | ``` 104 | ``` -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- ```go 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/mark3labs/mcp-go/server" 8 | "signaturit.com/mcp/internal/app" 9 | ) 10 | 11 | func main() { 12 | // Create the MCP server using our application logic. 13 | s := app.NewMCPServer() 14 | 15 | // Start the MCP server on stdio. 16 | if err := server.ServeStdio(s); err != nil { 17 | log.Fatalf("Server error: %v\n", err) 18 | } else { 19 | fmt.Println("MCP Server stopped gracefully.") 20 | } 21 | } 22 | ``` -------------------------------------------------------------------------------- /internal/app/server.go: -------------------------------------------------------------------------------- ```go 1 | package app 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/mark3labs/mcp-go/server" 7 | "signaturit.com/mcp/internal/handlers" 8 | "signaturit.com/mcp/internal/tools" 9 | ) 10 | 11 | // NewMCPServer creates and configures the MCP server. 12 | func NewMCPServer() *server.MCPServer { 13 | // Create a new MCP server. 14 | s := server.NewMCPServer( 15 | "Signaturit Tools Demo", 16 | "1.0.0", 17 | server.WithLogging(), 18 | ) 19 | 20 | // Get api key from environment variable 21 | apikey := os.Getenv("SIGNATURIT_SECRET_TOKEN") 22 | // Create a new handler for the signature tools. 23 | // This handler will be used to process the requests for the signature tools. 24 | h := handlers.NewHandler(apikey, false) 25 | 26 | // Register the signature tools. 27 | tools.InitSignatureTools(s, h) 28 | tools.InitContactTools(s, h) 29 | 30 | // Register the handlers for each tool. 31 | // The references to the handlers are set in InitSignatureTools. 32 | 33 | return s 34 | } 35 | ``` -------------------------------------------------------------------------------- /internal/tools/contact.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "signaturit.com/mcp/internal/handlers" 5 | 6 | "github.com/mark3labs/mcp-go/mcp" 7 | "github.com/mark3labs/mcp-go/server" 8 | ) 9 | 10 | // InitContactTools registers all contact-related MCP tools. 11 | func InitContactTools(s *server.MCPServer, handler *handlers.Handler) { 12 | // List Contacts Tool 13 | listContactsTool := mcp.NewTool( 14 | "list_contacts", 15 | mcp.WithDescription("Get all contacts from your Signaturit account"), 16 | ) 17 | s.AddTool(listContactsTool, handler.ListContacts) 18 | 19 | // Get Contact Tool 20 | getContactTool := mcp.NewTool( 21 | "get_contact", 22 | mcp.WithDescription("Get a single contact by ID"), 23 | mcp.WithString("contact_id", 24 | mcp.Required(), 25 | mcp.Description("ID of the contact to retrieve (e.g., e8125099-871e-11e6-88d5-06875124f8dd)"), 26 | ), 27 | ) 28 | s.AddTool(getContactTool, handler.GetContact) 29 | 30 | // Create Contact Tool 31 | createContactTool := mcp.NewTool( 32 | "create_contact", 33 | mcp.WithDescription("Create a new contact in your Signaturit account"), 34 | mcp.WithString("email", 35 | mcp.Required(), 36 | mcp.Description("Email of the new contact (e.g., [email protected])"), 37 | ), 38 | mcp.WithString("name", 39 | mcp.Required(), 40 | mcp.Description("Name of the new contact (e.g., John Doe)"), 41 | ), 42 | ) 43 | s.AddTool(createContactTool, handler.CreateContact) 44 | 45 | // Update Contact Tool 46 | updateContactTool := mcp.NewTool( 47 | "update_contact", 48 | mcp.WithDescription("Update an existing contact's information"), 49 | mcp.WithString("contact_id", 50 | mcp.Required(), 51 | mcp.Description("ID of the contact to update"), 52 | ), 53 | mcp.WithString("email", 54 | mcp.Description("New email for the contact (optional)"), 55 | ), 56 | mcp.WithString("name", 57 | mcp.Description("New name for the contact (optional)"), 58 | ), 59 | ) 60 | s.AddTool(updateContactTool, handler.UpdateContact) 61 | 62 | // Delete Contact Tool 63 | deleteContactTool := mcp.NewTool( 64 | "delete_contact", 65 | mcp.WithDescription("Delete a contact from your Signaturit account"), 66 | mcp.WithString("contact_id", 67 | mcp.Required(), 68 | mcp.Description("ID of the contact to delete"), 69 | ), 70 | ) 71 | s.AddTool(deleteContactTool, handler.DeleteContact) 72 | } 73 | ``` -------------------------------------------------------------------------------- /internal/handlers/http.go: -------------------------------------------------------------------------------- ```go 1 | package handlers 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | const ( 12 | sandboxURL = "https://api.sandbox.signaturit.com/v3" 13 | productionURL = "https://api.signaturit.com/v3" 14 | ) 15 | 16 | // SignaturitClient represents the HTTP client for Signaturit API 17 | type SignaturitClient struct { 18 | client *http.Client 19 | apiKey string 20 | baseURL string 21 | debug bool 22 | } 23 | 24 | // NewSignaturitClient creates a new Signaturit API client 25 | func NewSignaturitClient(apiKey string, debug bool) *SignaturitClient { 26 | baseURL := productionURL 27 | if debug { 28 | baseURL = sandboxURL 29 | } 30 | 31 | return &SignaturitClient{ 32 | client: &http.Client{}, 33 | apiKey: apiKey, 34 | baseURL: baseURL, 35 | debug: debug, 36 | } 37 | } 38 | 39 | // doRequest performs the HTTP request with authentication header 40 | func (c *SignaturitClient) doRequest(method, endpoint string, body interface{}) (*http.Response, error) { 41 | var bodyReader io.Reader 42 | 43 | if body != nil { 44 | jsonBody, err := json.Marshal(body) 45 | if err != nil { 46 | return nil, fmt.Errorf("error marshaling request body: %w", err) 47 | } 48 | bodyReader = bytes.NewBuffer(jsonBody) 49 | } 50 | 51 | url := fmt.Sprintf("%s%s", c.baseURL, endpoint) 52 | req, err := http.NewRequest(method, url, bodyReader) 53 | if err != nil { 54 | return nil, fmt.Errorf("error creating request: %w", err) 55 | } 56 | 57 | // Add authentication header 58 | req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) 59 | req.Header.Set("Content-Type", "application/json") 60 | 61 | resp, err := c.client.Do(req) 62 | if err != nil { 63 | return nil, fmt.Errorf("error executing request: %w", err) 64 | } 65 | 66 | return resp, nil 67 | } 68 | 69 | // Get performs a GET request 70 | func (c *SignaturitClient) Get(endpoint string) (*http.Response, error) { 71 | return c.doRequest(http.MethodGet, endpoint, nil) 72 | } 73 | 74 | // Post performs a POST request 75 | func (c *SignaturitClient) Post(endpoint string, body interface{}) (*http.Response, error) { 76 | return c.doRequest(http.MethodPost, endpoint, body) 77 | } 78 | 79 | // Patch performs a PATCH request 80 | func (c *SignaturitClient) Patch(endpoint string, body interface{}) (*http.Response, error) { 81 | return c.doRequest(http.MethodPatch, endpoint, body) 82 | } 83 | 84 | // Delete performs a DELETE request 85 | func (c *SignaturitClient) Delete(endpoint string) (*http.Response, error) { 86 | return c.doRequest(http.MethodDelete, endpoint, nil) 87 | } 88 | ``` -------------------------------------------------------------------------------- /internal/tools/signature.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "signaturit.com/mcp/internal/handlers" 5 | 6 | "github.com/mark3labs/mcp-go/mcp" 7 | "github.com/mark3labs/mcp-go/server" 8 | ) 9 | 10 | // InitSignatureTools registers all signature-related MCP tools. 11 | func InitSignatureTools(s *server.MCPServer, handler *handlers.Handler) { 12 | 13 | //Get Single Signature 14 | getSignatureTool := mcp.NewTool( 15 | "get_signature", 16 | mcp.WithDescription("Retrieve a single signature request by ID"), 17 | mcp.WithString("signature_id", 18 | mcp.Required(), 19 | mcp.Description("ID of the signature request to retrieve"), 20 | ), 21 | ) 22 | s.AddTool(getSignatureTool, handler.GetSignature) 23 | 24 | //Create Signature Request with Templates 25 | createSignatureTool := mcp.NewTool( 26 | "create_signature", 27 | mcp.WithDescription("Create a new signature request (multi-signer, with optional custom data) using templates instead of file uploads"), 28 | mcp.WithString("templates", 29 | mcp.Required(), 30 | mcp.Description("Comma-separated list of template IDs or hashtags to use for the signature request. For example: #NDA,abc123"), 31 | ), 32 | mcp.WithString("recipients", 33 | mcp.Required(), 34 | mcp.Description("List of signer objects, each with name, email (or phone if SMS), and optional advanced settings"), 35 | ), 36 | mcp.WithString("body", 37 | mcp.Description("Body message for the email or SMS (HTML allowed in email). OPTIONAL"), 38 | ), 39 | mcp.WithString("subject", 40 | mcp.Description("Subject for the email request. OPTIONAL"), 41 | ), 42 | mcp.WithString("type", 43 | mcp.Description("Delivery type: 'email' (default), 'sms', or 'wizard'. OPTIONAL"), 44 | mcp.DefaultString("email"), 45 | ), 46 | mcp.WithNumber("expires_in_days", 47 | mcp.Description("Number of days before the signature request expires (1–365). OPTIONAL"), 48 | ), 49 | mcp.WithString("event_url", 50 | mcp.Description("Callback URL for receiving real-time notifications. OPTIONAL"), 51 | ), 52 | mcp.WithString("signing_mode", 53 | mcp.Description("Signing order: 'sequential' (default) or 'parallel'. OPTIONAL"), 54 | ), 55 | ) 56 | s.AddTool(createSignatureTool, handler.CreateSignature) 57 | 58 | //Send Reminder 59 | sendSignatureReminderTool := mcp.NewTool( 60 | "send_signature_reminder", 61 | mcp.WithDescription("Send a reminder email/SMS to the signer of a pending signature"), 62 | mcp.WithString("signature_id", 63 | mcp.Required(), 64 | mcp.Description("ID of the signature request to remind"), 65 | ), 66 | ) 67 | s.AddTool(sendSignatureReminderTool, handler.SendSignatureReminder) 68 | 69 | //Cancel Signature Request 70 | cancelSignatureTool := mcp.NewTool( 71 | "cancel_signature", 72 | mcp.WithDescription("Cancel an in-progress signature so it can no longer be signed"), 73 | mcp.WithString("signature_id", 74 | mcp.Required(), 75 | mcp.Description("ID of the signature request to cancel"), 76 | ), 77 | mcp.WithString("reason", 78 | mcp.Description("Optional reason for canceling the signature request"), 79 | ), 80 | ) 81 | s.AddTool(cancelSignatureTool, handler.CancelSignature) 82 | } 83 | ``` -------------------------------------------------------------------------------- /internal/handlers/contact.go: -------------------------------------------------------------------------------- ```go 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | 10 | "github.com/mark3labs/mcp-go/mcp" 11 | ) 12 | 13 | type Contact struct { 14 | ID string `json:"id"` 15 | Email string `json:"email"` 16 | Name string `json:"name"` 17 | CreatedAt string `json:"created_at"` 18 | } 19 | 20 | func (h *Handler) ListContacts(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 21 | resp, err := h.client.Get("/contacts.json") 22 | if err != nil { 23 | return nil, fmt.Errorf("failed to list contacts: %w", err) 24 | } 25 | defer resp.Body.Close() 26 | 27 | body, err := io.ReadAll(resp.Body) 28 | if err != nil { 29 | return nil, fmt.Errorf("failed to read response: %w", err) 30 | } 31 | 32 | if resp.StatusCode != http.StatusOK { 33 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) 34 | } 35 | 36 | var contacts []Contact 37 | if err := json.Unmarshal(body, &contacts); err != nil { 38 | return nil, fmt.Errorf("failed to parse response: %w", err) 39 | } 40 | 41 | summary := "Contacts:\n" 42 | for _, contact := range contacts { 43 | summary += fmt.Sprintf("- %s (%s) [ID: %s]\n", contact.Name, contact.Email, contact.ID) 44 | } 45 | 46 | return mcp.NewToolResultText(summary), nil 47 | } 48 | 49 | func (h *Handler) GetContact(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 50 | contactID := req.Params.Arguments["contact_id"].(string) 51 | 52 | resp, err := h.client.Get(fmt.Sprintf("/contacts/%s.json", contactID)) 53 | if err != nil { 54 | return nil, fmt.Errorf("failed to get contact: %w", err) 55 | } 56 | defer resp.Body.Close() 57 | 58 | body, err := io.ReadAll(resp.Body) 59 | if err != nil { 60 | return nil, fmt.Errorf("failed to read response: %w", err) 61 | } 62 | 63 | if resp.StatusCode != http.StatusOK { 64 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) 65 | } 66 | 67 | var contact Contact 68 | if err := json.Unmarshal(body, &contact); err != nil { 69 | return nil, fmt.Errorf("failed to parse response: %w", err) 70 | } 71 | 72 | return mcp.NewToolResultText(fmt.Sprintf("Contact: %s (%s) [ID: %s]", contact.Name, contact.Email, contact.ID)), nil 73 | } 74 | 75 | func (h *Handler) CreateContact(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 76 | email := req.Params.Arguments["email"].(string) 77 | name := req.Params.Arguments["name"].(string) 78 | 79 | requestBody := map[string]interface{}{ 80 | "email": email, 81 | "name": name, 82 | } 83 | 84 | resp, err := h.client.Post("/contacts.json", requestBody) 85 | if err != nil { 86 | return nil, fmt.Errorf("failed to create contact: %w", err) 87 | } 88 | defer resp.Body.Close() 89 | 90 | body, err := io.ReadAll(resp.Body) 91 | if err != nil { 92 | return nil, fmt.Errorf("failed to read response: %w", err) 93 | } 94 | 95 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { 96 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) 97 | } 98 | 99 | var contact Contact 100 | if err := json.Unmarshal(body, &contact); err != nil { 101 | return nil, fmt.Errorf("failed to parse response: %w", err) 102 | } 103 | 104 | return mcp.NewToolResultText(fmt.Sprintf("Contact created: %s (%s) [ID: %s]", contact.Name, contact.Email, contact.ID)), nil 105 | } 106 | 107 | func (h *Handler) UpdateContact(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 108 | contactID := req.Params.Arguments["contact_id"].(string) 109 | 110 | requestBody := make(map[string]interface{}) 111 | if email, ok := req.Params.Arguments["email"].(string); ok && email != "" { 112 | requestBody["email"] = email 113 | } 114 | if name, ok := req.Params.Arguments["name"].(string); ok && name != "" { 115 | requestBody["name"] = name 116 | } 117 | 118 | resp, err := h.client.Patch(fmt.Sprintf("/contacts/%s.json", contactID), requestBody) 119 | if err != nil { 120 | return nil, fmt.Errorf("failed to update contact: %w", err) 121 | } 122 | defer resp.Body.Close() 123 | 124 | body, err := io.ReadAll(resp.Body) 125 | if err != nil { 126 | return nil, fmt.Errorf("failed to read response: %w", err) 127 | } 128 | 129 | if resp.StatusCode != http.StatusOK { 130 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) 131 | } 132 | 133 | var contact Contact 134 | if err := json.Unmarshal(body, &contact); err != nil { 135 | return nil, fmt.Errorf("failed to parse response: %w", err) 136 | } 137 | 138 | return mcp.NewToolResultText(fmt.Sprintf("Contact updated: %s (%s) [ID: %s]", contact.Name, contact.Email, contact.ID)), nil 139 | } 140 | 141 | func (h *Handler) DeleteContact(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 142 | contactID := req.Params.Arguments["contact_id"].(string) 143 | 144 | resp, err := h.client.Delete(fmt.Sprintf("/contacts/%s.json", contactID)) 145 | if err != nil { 146 | return nil, fmt.Errorf("failed to delete contact: %w", err) 147 | } 148 | defer resp.Body.Close() 149 | 150 | if resp.StatusCode != http.StatusOK { 151 | body, _ := io.ReadAll(resp.Body) 152 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) 153 | } 154 | 155 | return mcp.NewToolResultText(fmt.Sprintf("Contact %s successfully deleted", contactID)), nil 156 | } 157 | ``` -------------------------------------------------------------------------------- /internal/handlers/signature.go: -------------------------------------------------------------------------------- ```go 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/mark3labs/mcp-go/mcp" 13 | ) 14 | 15 | type Recipient struct { 16 | Name string `json:"name"` 17 | Email string `json:"email"` 18 | } 19 | 20 | type ResponseGetSignature struct { 21 | CreatedAt string `json:"created_at"` 22 | Data []any `json:"data"` 23 | Documents []Document `json:"documents"` 24 | } 25 | 26 | type Document struct { 27 | Email string `json:"email"` 28 | Events []Event `json:"events"` 29 | File File `json:"file"` 30 | ID string `json:"id"` // Document-level ID 31 | Name string `json:"name"` 32 | Status string `json:"status"` 33 | } 34 | 35 | type Event struct { 36 | CreatedAt string `json:"created_at"` 37 | Type string `json:"type"` 38 | } 39 | 40 | type File struct { 41 | ID string `json:"id"` // File-level ID 42 | Name string `json:"name"` 43 | Pages int `json:"pages"` 44 | Size int `json:"size"` 45 | } 46 | 47 | // Handler handles signature-related operations 48 | type Handler struct { 49 | client *SignaturitClient 50 | } 51 | 52 | // NewHandler creates a new signature handler 53 | func NewHandler(apiKey string, debug bool) *Handler { 54 | return &Handler{ 55 | client: NewSignaturitClient(apiKey, debug), 56 | } 57 | } 58 | 59 | func (h *Handler) GetSignature(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 60 | signatureID := req.Params.Arguments["signature_id"].(string) 61 | 62 | // Make API call 63 | resp, err := h.client.Get(fmt.Sprintf("/signatures/%s.json", signatureID)) 64 | if err != nil { 65 | return nil, fmt.Errorf("failed to get signature: %w", err) 66 | } 67 | defer resp.Body.Close() 68 | 69 | // Read response 70 | body, err := io.ReadAll(resp.Body) 71 | if err != nil { 72 | return nil, fmt.Errorf("failed to read response: %w", err) 73 | } 74 | 75 | // Check response status 76 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { 77 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) 78 | } 79 | 80 | // Parse response 81 | var response ResponseGetSignature 82 | if err := json.Unmarshal(body, &response); err != nil { 83 | return nil, fmt.Errorf("failed to parse response: %w", err) 84 | } 85 | 86 | ready := true 87 | summary := "" 88 | for _, document := range response.Documents { 89 | summary += fmt.Sprintf("Document %s: Send to %s is %s\n", document.ID, document.Email, document.Status) 90 | for _, event := range document.Events { 91 | summary += fmt.Sprintf(" - %s at %s\n", event.Type, event.CreatedAt) 92 | } 93 | if document.Status != "completed" { 94 | ready = false 95 | } 96 | } 97 | 98 | return mcp.NewToolResultText(fmt.Sprintf("Signature ID %s created at %s, summary:\n%s\nComplete:%s", signatureID, response.CreatedAt, summary, strconv.FormatBool(ready))), nil 99 | } 100 | 101 | func (h *Handler) CreateSignature(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 102 | st := req.Params.Arguments["templates"].(string) 103 | templates := strings.Split(st, ",") 104 | sr := req.Params.Arguments["recipients"].(string) 105 | expiresInDays, errEx := req.Params.Arguments["expires_in_days"].(float64) 106 | if !errEx { 107 | expiresInDays = 7 108 | } 109 | 110 | var recipients []Recipient 111 | if err := json.Unmarshal([]byte(sr), &recipients); err != nil { 112 | return nil, fmt.Errorf("failed to parse recipients: %w", err) 113 | } 114 | 115 | // Prepare request body 116 | requestBody := map[string]interface{}{ 117 | "templates": templates, 118 | "recipients": recipients, 119 | "expires_in": expiresInDays, 120 | "body": req.Params.Arguments["body"].(string), 121 | "subject": req.Params.Arguments["subject"].(string), 122 | } 123 | 124 | // Make API call 125 | resp, err := h.client.Post("/signatures.json", requestBody) 126 | if err != nil { 127 | return nil, fmt.Errorf("failed to create signature: %w", err) 128 | } 129 | defer resp.Body.Close() 130 | 131 | // Read response 132 | body, err := io.ReadAll(resp.Body) 133 | if err != nil { 134 | return nil, fmt.Errorf("failed to read response: %w", err) 135 | } 136 | 137 | // Check response status 138 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { 139 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) 140 | } 141 | 142 | return mcp.NewToolResultText(fmt.Sprintf("Templates %v was send to %v to sign with reponse: %s", templates, recipients, string(body))), nil 143 | } 144 | 145 | func (h *Handler) SendSignatureReminder(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 146 | signatureID := req.Params.Arguments["signature_id"].(string) 147 | resp, err := h.client.Post(fmt.Sprintf("/signatures/%s/reminders.json", signatureID), nil) 148 | if err != nil { 149 | return nil, fmt.Errorf("failed to send reminder: %w", err) 150 | } 151 | defer resp.Body.Close() 152 | 153 | // Check response status 154 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { 155 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode) 156 | } 157 | 158 | return mcp.NewToolResultText(fmt.Sprintf("Sending reminder for signature %s...", signatureID)), nil 159 | } 160 | 161 | func (h *Handler) CancelSignature(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { 162 | signatureID := req.Params.Arguments["signature_id"].(string) 163 | reason, _ := req.Params.Arguments["reason"].(string) 164 | 165 | resp, err := h.client.Patch(fmt.Sprintf("/signatures/%s.json", signatureID), map[string]interface{}{"reason": reason}) 166 | if err != nil { 167 | return nil, fmt.Errorf("failed to cancel signature: %w", err) 168 | } 169 | defer resp.Body.Close() 170 | 171 | // Check response status 172 | if resp.StatusCode != http.StatusOK { 173 | return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode) 174 | } 175 | 176 | return mcp.NewToolResultText(fmt.Sprintf("Canceling signature %s. Reason: %s", signatureID, reason)), nil 177 | } 178 | ```