MCP Server for eSignatures

by esignaturescom
Verified
  • src
  • mcp_server_esignatures
from os import getenv import asyncio import httpx import json import click from mcp.server.models import InitializationOptions import mcp.types as types from mcp.server import NotificationOptions, Server from pydantic import AnyUrl import mcp.server.stdio from .input_schema_contracts import INPUT_SCHEMA_CREATE_CONTRACT, INPUT_SCHEMA_QUERY_CONTRACT, INPUT_SCHEMA_CREATE_CONTRACT, INPUT_SCHEMA_WITHDRAW_CONTRACT, INPUT_SCHEMA_DELETE_CONTRACT, INPUT_SCHEMA_LIST_RECENT_CONTRACTS from .input_schema_templates import INPUT_SCHEMA_CREATE_TEMPLATE, INPUT_SCHEMA_QUERY_TEMPLATE, INPUT_SCHEMA_UPDATE_TEMPLATE, INPUT_SCHEMA_DELETE_TEMPLATE, INPUT_SCHEMA_LIST_TEMPLATES from .input_schema_template_collaborators import INPUT_SCHEMA_ADD_TEMPLATE_COLLABORATOR, INPUT_SCHEMA_REMOVE_TEMPLATE_COLLABORATOR, INPUT_SCHEMA_LIST_TEMPLATE_COLLABORATORS ESIGNATURES_SECRET_TOKEN = getenv("ESIGNATURES_SECRET_TOKEN") ESIGNATURES_API_BASE = "https://esignatures.com" async def serve() -> Server: secret_token = ESIGNATURES_SECRET_TOKEN server = Server("mcp-server-esignatures") httpxClient = httpx.AsyncClient(base_url=ESIGNATURES_API_BASE) @server.list_tools() async def handle_list_tools() -> list[types.Tool]: return [ types.Tool( name="create_contract", description="Creates a new contract. The contract can be a draft which the user can customize/send, or the contract can be sent instantly. So called 'signature fields' like Name/Date/signature-line must be left out, they are all handled automatically. Contract owners can customize the content by replacing {{placeholder fields}} inside the content, and the signers can fill in Signer fields when they sign the contract.", inputSchema=INPUT_SCHEMA_CREATE_CONTRACT ), types.Tool( name="query_contract", description="Responds with the contract details, contract_id, status, final PDF url if present, title, labels, metadata, expiry time if present, and signer details with all signer events (signer events are included only for recent contracts, with rate limiting).", inputSchema=INPUT_SCHEMA_QUERY_CONTRACT ), types.Tool( name="withdraw_contract", description="Withdraws a sent contract.", inputSchema=INPUT_SCHEMA_WITHDRAW_CONTRACT ), types.Tool( name="delete_contract", description="Deletes a contract. The contract can only be deleted if it's a test contract or a draft contract.", inputSchema=INPUT_SCHEMA_DELETE_CONTRACT ), types.Tool( name="list_recent_contracts", description="Returns the the details of the latest 100 contracts.", inputSchema=INPUT_SCHEMA_LIST_RECENT_CONTRACTS ), types.Tool( name="create_template", description="Creates a reusable contract template for contracts to be based on.", inputSchema=INPUT_SCHEMA_CREATE_TEMPLATE ), types.Tool( name="update_template", description="Updates the title, labels or the content of a contract template.", inputSchema=INPUT_SCHEMA_UPDATE_TEMPLATE ), types.Tool( name="query_template", description="Responds with the template details, template_id, title, labels, created_at, list of the Placeholder fields in the template, list of Signer fields int he template, and the full content inside document_elements", inputSchema=INPUT_SCHEMA_QUERY_TEMPLATE ), types.Tool( name="delete_template", description="Deletes a contract template.", inputSchema=INPUT_SCHEMA_DELETE_TEMPLATE ), types.Tool( name="list_templates", description="Lists the templates.", inputSchema=INPUT_SCHEMA_LIST_TEMPLATES ), types.Tool( name="add_template_collaborator", description="Creates a HTTPS link for editing a contract template; sends an invitation email if an email is provided..", inputSchema=INPUT_SCHEMA_ADD_TEMPLATE_COLLABORATOR ), types.Tool( name="remove_template_collaborator", description="Removes the template collaborator", inputSchema=INPUT_SCHEMA_REMOVE_TEMPLATE_COLLABORATOR ), types.Tool( name="list_template_collaborators", description="Returns the list of template collaborators, including their GUID, name, email, and the HTTPS link for editing the template", inputSchema=INPUT_SCHEMA_LIST_TEMPLATE_COLLABORATORS ) ] @server.call_tool() async def handle_call_tool( name: str, arguments: dict | None ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: if name == "create_contract": response = await httpxClient.post(f"/api/contracts?token={secret_token}&source=mcpserver", json=arguments) return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "query_contract": response = await httpxClient.get(f"/api/contracts/{arguments.get('contract_id')}?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "withdraw_contract": response = await httpxClient.post(f"/api/contracts/{arguments.get('contract_id')}/withdraw?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "delete_contract": response = await httpxClient.post(f"/api/contracts/{arguments.get('contract_id')}/delete?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "list_recent_contracts": response = await httpxClient.get(f"/api/contracts/recent?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "create_template": response = await httpxClient.post(f"/api/templates?token={secret_token}", json=arguments) return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "query_template": response = await httpxClient.get(f"/api/templates/{arguments.get('template_id')}?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "update_template": response = await httpxClient.post(f"/api/templates/{arguments.get('template_id')}?token={secret_token}", json=arguments) return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "delete_template": response = await httpxClient.post(f"/api/templates/{arguments.get('template_id')}/delete?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "list_templates": response = await httpxClient.get(f"/api/templates?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "add_template_collaborator": response = await httpxClient.post(f"/api/templates/{arguments.get('template_id')}/collaborators?token={secret_token}", json=arguments) return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "remove_template_collaborator": response = await httpxClient.post(f"/api/templates/{arguments.get('template_id')}/collaborators/{arguments.get('template_collaborator_id')}/remove?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] if name == "list_template_collaborators": response = await httpxClient.get(f"/api/templates/{arguments.get('template_id')}/collaborators?token={secret_token}") return [types.TextContent(type="text", text=f"Response code: {response.status_code}, response: {response.json()}")] raise ValueError(f"Unknown tool: {name}") return server def main(): async def _run(): # Run the server using stdin/stdout streams async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): server = await serve() await server.run( read_stream, write_stream, InitializationOptions( server_name="mcp-server-esignatures", server_version="0.1.0", capabilities=server.get_capabilities( notification_options=NotificationOptions(), experimental_capabilities={}, ), ), ) asyncio.run(_run())