#
tokens: 8383/50000 9/9 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | 
```