docura-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # docura-mcp
2
+
3
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for the Docura e-signature platform. Connect Claude Code, Cursor, or any MCP-compatible AI client to your Docura account and manage documents via natural language.
4
+
5
+ No build step required — `npx` downloads and runs it automatically.
6
+
7
+ ## What you can do
8
+
9
+ Once connected, ask your AI assistant things like:
10
+
11
+ - *"List all my templates"*
12
+ - *"Send the Master Services Agreement to alice@company.com and bob@company.com for signing"*
13
+ - *"What's the status of submission abc123? Has everyone signed?"*
14
+ - *"Resend the invitation to the second signer on the Smith contract"*
15
+ - *"Void the Q3 NDA submission"*
16
+ - *"Get the full audit trail for submission xyz"*
17
+ - *"How do I download the signed PDF for the Johnson agreement?"*
18
+ - *"List all our registered webhooks and test the production one"*
19
+
20
+ ## Prerequisites
21
+
22
+ - Node.js 18+
23
+ - A Docura API key (`sk_live_*`) — generate one at **Settings → API Keys** in the dashboard
24
+
25
+ ## Setup
26
+
27
+ ### Step 1 — Get an API key
28
+
29
+ Log in to your Docura dashboard → **Settings → API Keys** → create a new key. It starts with `sk_live_`.
30
+
31
+ ### Step 2 — Add to Claude Code
32
+
33
+ Add the server to `.claude/settings.local.json` in your project:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "docura": {
39
+ "command": "npx",
40
+ "args": ["-y", "docura-mcp"],
41
+ "env": {
42
+ "DOCURA_API_KEY": "sk_live_your_key_here",
43
+ "DOCURA_BASE_URL": "https://app.docura.com"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ Restart Claude Code. The `docura` server appears in the MCP panel.
51
+
52
+ ### Add to Cursor
53
+
54
+ Go to **Cursor Settings → MCP** and add the same block above.
55
+
56
+ ### Install globally (optional — skips the npx download delay)
57
+
58
+ ```bash
59
+ npm install -g docura-mcp
60
+ ```
61
+
62
+ Then use `"command": "docura-mcp"` (no `args`) in your MCP config instead of the `npx` form.
63
+
64
+ ## Environment variables
65
+
66
+ | Variable | Required | Default | Description |
67
+ |----------|----------|---------|-------------|
68
+ | `DOCURA_API_KEY` | Yes | — | API key from Docura Settings → API Keys |
69
+ | `DOCURA_BASE_URL` | No | `http://localhost:3000` | Base URL of your Docura instance |
70
+
71
+ Set `DOCURA_BASE_URL` to your deployed URL (e.g. `https://app.docura.com`) for production use.
72
+
73
+ ## Available tools
74
+
75
+ ### Templates
76
+
77
+ | Tool | Description |
78
+ |------|-------------|
79
+ | `list_templates` | List all PDF templates in your organization |
80
+ | `get_template` | Get a template's details, fields, and signer roles |
81
+
82
+ ### Submissions
83
+
84
+ | Tool | Description |
85
+ |------|-------------|
86
+ | `list_submissions` | List submissions, optionally filtered by status |
87
+ | `get_submission` | Get status and signer progress for a submission |
88
+ | `create_submission` | Send a template to one or more signers |
89
+ | `void_submission` | Cancel a pending submission |
90
+ | `resend_invitation` | Resend the signing email to a specific signer |
91
+
92
+ ### Audit & download
93
+
94
+ | Tool | Description |
95
+ |------|-------------|
96
+ | `get_audit_trail` | Full cryptographic event log (timestamps, IPs, hashes) |
97
+ | `get_download_url` | Get the URL + curl command to download a completed signed PDF |
98
+
99
+ ### Webhooks
100
+
101
+ | Tool | Description |
102
+ |------|-------------|
103
+ | `list_webhooks` | List all registered webhook endpoints |
104
+ | `test_webhook` | Send a test event to verify a webhook is reachable |
105
+
106
+ ## Tool reference
107
+
108
+ ### `create_submission`
109
+
110
+ Sends a document to signers. The `signers` array must contain one entry per signer role defined in the template. Use `get_template` first to see the available role names.
111
+
112
+ ```json
113
+ {
114
+ "templateId": "tmpl_abc123",
115
+ "signers": [
116
+ { "email": "alice@company.com", "name": "Alice", "roleName": "Client" },
117
+ { "email": "bob@company.com", "name": "Bob", "roleName": "Vendor" }
118
+ ],
119
+ "message": "Please review and sign at your earliest convenience.",
120
+ "expiresAt": "2025-12-31T23:59:59Z"
121
+ }
122
+ ```
123
+
124
+ ### `resend_invitation`
125
+
126
+ Resends the signing email to a signer who hasn't signed yet. Get the `signerId` from `get_submission`.
127
+
128
+ ```json
129
+ {
130
+ "submissionId": "sub_xyz789",
131
+ "signerId": "sgn_def456"
132
+ }
133
+ ```
134
+
135
+ ### `get_download_url`
136
+
137
+ Returns a URL and curl command for downloading the completed signed PDF. The submission must have status `COMPLETED`.
138
+
139
+ ```json
140
+ { "submissionId": "sub_xyz789" }
141
+ ```
142
+
143
+ Response:
144
+ ```json
145
+ {
146
+ "downloadUrl": "https://app.docura.com/api/v1/submissions/sub_xyz789/download",
147
+ "instructions": "curl -H \"Authorization: Bearer $DOCURA_API_KEY\" \"...\" -o signed.pdf"
148
+ }
149
+ ```
150
+
151
+ ## Development
152
+
153
+ ```bash
154
+ # Clone the repo and build from source
155
+ cd mcp-server
156
+ npm install
157
+ npm run build
158
+ node dist/index.js
159
+
160
+ # Run without building (uses tsx)
161
+ npm run dev
162
+ ```
163
+
164
+ To test the server manually with [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
165
+
166
+ ```bash
167
+ npx @modelcontextprotocol/inspector node dist/index.js
168
+ ```
169
+
170
+ Set `DOCURA_API_KEY` and optionally `DOCURA_BASE_URL` in your shell before running.
@@ -0,0 +1,6 @@
1
+ declare const BASE_URL: string;
2
+ declare const API_KEY: string;
3
+ export declare function apiGet(path: string): Promise<unknown>;
4
+ export declare function apiPost(path: string, body?: unknown): Promise<unknown>;
5
+ export declare function apiDelete(path: string): Promise<unknown>;
6
+ export { BASE_URL, API_KEY };
package/dist/client.js ADDED
@@ -0,0 +1,35 @@
1
+ const BASE_URL = (process.env.DOCURA_BASE_URL ?? "http://localhost:3000").replace(/\/$/, "");
2
+ const API_KEY = process.env.DOCURA_API_KEY ?? "";
3
+ function headers() {
4
+ return {
5
+ Authorization: `Bearer ${API_KEY}`,
6
+ "Content-Type": "application/json",
7
+ };
8
+ }
9
+ async function checkResponse(res) {
10
+ if (!res.ok) {
11
+ const body = await res.text();
12
+ throw new Error(`Docura API ${res.status}: ${body}`);
13
+ }
14
+ return res.json();
15
+ }
16
+ export async function apiGet(path) {
17
+ const res = await fetch(`${BASE_URL}/api/v1${path}`, { headers: headers() });
18
+ return checkResponse(res);
19
+ }
20
+ export async function apiPost(path, body) {
21
+ const res = await fetch(`${BASE_URL}/api/v1${path}`, {
22
+ method: "POST",
23
+ headers: headers(),
24
+ body: body !== undefined ? JSON.stringify(body) : undefined,
25
+ });
26
+ return checkResponse(res);
27
+ }
28
+ export async function apiDelete(path) {
29
+ const res = await fetch(`${BASE_URL}/api/v1${path}`, {
30
+ method: "DELETE",
31
+ headers: headers(),
32
+ });
33
+ return checkResponse(res);
34
+ }
35
+ export { BASE_URL, API_KEY };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { templateTools, handleTemplateTools } from "./tools/templates.js";
6
+ import { submissionTools, handleSubmissionTools } from "./tools/submissions.js";
7
+ import { auditTools, handleAuditTools } from "./tools/audit.js";
8
+ import { webhookTools, handleWebhookTools } from "./tools/webhooks.js";
9
+ if (!process.env.DOCURA_API_KEY) {
10
+ process.stderr.write("Error: DOCURA_API_KEY environment variable is required.\n" +
11
+ "Get your API key from: Docura Dashboard → Settings → API Keys\n");
12
+ process.exit(1);
13
+ }
14
+ const allTools = [
15
+ ...templateTools,
16
+ ...submissionTools,
17
+ ...auditTools,
18
+ ...webhookTools,
19
+ ];
20
+ const toolNameSet = new Set(allTools.map((t) => t.name));
21
+ const server = new Server({ name: "docura", version: "1.0.0" }, { capabilities: { tools: {} } });
22
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: allTools }));
23
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
24
+ const { name, arguments: args = {} } = request.params;
25
+ if (!toolNameSet.has(name)) {
26
+ return {
27
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
28
+ isError: true,
29
+ };
30
+ }
31
+ try {
32
+ let result;
33
+ const a = args;
34
+ if (templateTools.some((t) => t.name === name)) {
35
+ result = await handleTemplateTools(name, a);
36
+ }
37
+ else if (submissionTools.some((t) => t.name === name)) {
38
+ result = await handleSubmissionTools(name, a);
39
+ }
40
+ else if (auditTools.some((t) => t.name === name)) {
41
+ result = await handleAuditTools(name, a);
42
+ }
43
+ else {
44
+ result = await handleWebhookTools(name, a);
45
+ }
46
+ return {
47
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
48
+ };
49
+ }
50
+ catch (err) {
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ return {
53
+ content: [{ type: "text", text: `Error: ${message}` }],
54
+ isError: true,
55
+ };
56
+ }
57
+ });
58
+ const transport = new StdioServerTransport();
59
+ await server.connect(transport);
@@ -0,0 +1,3 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const auditTools: Tool[];
3
+ export declare function handleAuditTools(name: string, args: Record<string, unknown>): Promise<unknown>;
@@ -0,0 +1,45 @@
1
+ import { apiGet, BASE_URL } from "../client.js";
2
+ export const auditTools = [
3
+ {
4
+ name: "get_audit_trail",
5
+ description: "Get the cryptographic audit trail for a submission — all events (invited, viewed, signed, declined, voided) with timestamps, IP addresses, and integrity metadata",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ submissionId: { type: "string", description: "Submission ID" },
10
+ },
11
+ required: ["submissionId"],
12
+ },
13
+ },
14
+ {
15
+ name: "get_download_url",
16
+ description: "Get instructions and the URL to download the completed signed PDF for a submission. The submission must have status COMPLETED.",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ submissionId: {
21
+ type: "string",
22
+ description: "Submission ID (must be COMPLETED)",
23
+ },
24
+ },
25
+ required: ["submissionId"],
26
+ },
27
+ },
28
+ ];
29
+ export async function handleAuditTools(name, args) {
30
+ switch (name) {
31
+ case "get_audit_trail": {
32
+ return apiGet(`/submissions/${args.submissionId}/audit`);
33
+ }
34
+ case "get_download_url": {
35
+ const url = `${BASE_URL}/api/v1/submissions/${args.submissionId}/download`;
36
+ return {
37
+ downloadUrl: url,
38
+ instructions: `Send a GET request to the URL with your API key in the Authorization header:\n` +
39
+ `curl -H "Authorization: Bearer $DOCURA_API_KEY" "${url}" -o signed.pdf`,
40
+ };
41
+ }
42
+ default:
43
+ throw new Error(`Unknown tool: ${name}`);
44
+ }
45
+ }
@@ -0,0 +1,3 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const submissionTools: Tool[];
3
+ export declare function handleSubmissionTools(name: string, args: Record<string, unknown>): Promise<unknown>;
@@ -0,0 +1,124 @@
1
+ import { apiGet, apiPost, apiDelete } from "../client.js";
2
+ export const submissionTools = [
3
+ {
4
+ name: "list_submissions",
5
+ description: "List document submissions in your organization. Optionally filter by status.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ status: {
10
+ type: "string",
11
+ enum: ["DRAFT", "PENDING", "COMPLETED", "DECLINED", "EXPIRED", "VOIDED"],
12
+ description: "Filter by submission status",
13
+ },
14
+ page: { type: "number", description: "Page number (default: 1)" },
15
+ limit: { type: "number", description: "Results per page, max 100 (default: 20)" },
16
+ },
17
+ },
18
+ },
19
+ {
20
+ name: "get_submission",
21
+ description: "Get the status and details of a specific submission, including each signer's progress",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {
25
+ submissionId: { type: "string", description: "Submission ID" },
26
+ },
27
+ required: ["submissionId"],
28
+ },
29
+ },
30
+ {
31
+ name: "create_submission",
32
+ description: "Send a document for signing by creating a submission from a template. Returns signing URLs for each signer.",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {
36
+ templateId: { type: "string", description: "ID of the template to send" },
37
+ signers: {
38
+ type: "array",
39
+ description: "List of signers — one per signer role in the template",
40
+ items: {
41
+ type: "object",
42
+ properties: {
43
+ email: { type: "string", description: "Signer email address" },
44
+ name: { type: "string", description: "Signer display name (optional)" },
45
+ roleName: {
46
+ type: "string",
47
+ description: "Signer role name — must match a role defined in the template",
48
+ },
49
+ },
50
+ required: ["email", "roleName"],
51
+ },
52
+ },
53
+ message: {
54
+ type: "string",
55
+ description: "Optional message to include in the signing invitation email",
56
+ },
57
+ expiresAt: {
58
+ type: "string",
59
+ description: "Optional expiry date in ISO 8601 format (e.g. 2025-12-31T23:59:59Z)",
60
+ },
61
+ },
62
+ required: ["templateId", "signers"],
63
+ },
64
+ },
65
+ {
66
+ name: "void_submission",
67
+ description: "Cancel (void) a pending submission so signers can no longer sign it",
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ submissionId: { type: "string", description: "Submission ID to void" },
72
+ },
73
+ required: ["submissionId"],
74
+ },
75
+ },
76
+ {
77
+ name: "resend_invitation",
78
+ description: "Resend the signing invitation email to a specific signer who hasn't signed yet",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ submissionId: { type: "string", description: "Submission ID" },
83
+ signerId: {
84
+ type: "string",
85
+ description: "Signer ID — use get_submission to find signer IDs",
86
+ },
87
+ },
88
+ required: ["submissionId", "signerId"],
89
+ },
90
+ },
91
+ ];
92
+ export async function handleSubmissionTools(name, args) {
93
+ switch (name) {
94
+ case "list_submissions": {
95
+ const params = new URLSearchParams();
96
+ if (args.status)
97
+ params.set("status", String(args.status));
98
+ if (args.page)
99
+ params.set("page", String(args.page));
100
+ if (args.limit)
101
+ params.set("limit", String(args.limit));
102
+ const qs = params.toString();
103
+ return apiGet(`/submissions${qs ? `?${qs}` : ""}`);
104
+ }
105
+ case "get_submission": {
106
+ return apiGet(`/submissions/${args.submissionId}`);
107
+ }
108
+ case "create_submission": {
109
+ return apiPost(`/templates/${args.templateId}/submissions`, {
110
+ signers: args.signers,
111
+ ...(args.message ? { message: args.message } : {}),
112
+ ...(args.expiresAt ? { expiresAt: args.expiresAt } : {}),
113
+ });
114
+ }
115
+ case "void_submission": {
116
+ return apiDelete(`/submissions/${args.submissionId}`);
117
+ }
118
+ case "resend_invitation": {
119
+ return apiPost(`/submissions/${args.submissionId}/signers/${args.signerId}/resend`);
120
+ }
121
+ default:
122
+ throw new Error(`Unknown tool: ${name}`);
123
+ }
124
+ }
@@ -0,0 +1,3 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const templateTools: Tool[];
3
+ export declare function handleTemplateTools(name: string, args: Record<string, unknown>): Promise<unknown>;
@@ -0,0 +1,39 @@
1
+ import { apiGet } from "../client.js";
2
+ export const templateTools = [
3
+ {
4
+ name: "list_templates",
5
+ description: "List all PDF templates in your Docura organization",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ page: { type: "number", description: "Page number (default: 1)" },
10
+ limit: { type: "number", description: "Results per page, max 100 (default: 20)" },
11
+ },
12
+ },
13
+ },
14
+ {
15
+ name: "get_template",
16
+ description: "Get details of a specific template including its fields and signer roles",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ templateId: { type: "string", description: "Template ID" },
21
+ },
22
+ required: ["templateId"],
23
+ },
24
+ },
25
+ ];
26
+ export async function handleTemplateTools(name, args) {
27
+ switch (name) {
28
+ case "list_templates": {
29
+ const page = args.page ?? 1;
30
+ const limit = args.limit ?? 20;
31
+ return apiGet(`/templates?page=${page}&limit=${limit}`);
32
+ }
33
+ case "get_template": {
34
+ return apiGet(`/templates/${args.templateId}`);
35
+ }
36
+ default:
37
+ throw new Error(`Unknown tool: ${name}`);
38
+ }
39
+ }
@@ -0,0 +1,3 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const webhookTools: Tool[];
3
+ export declare function handleWebhookTools(name: string, args: Record<string, unknown>): Promise<unknown>;
@@ -0,0 +1,34 @@
1
+ import { apiGet, apiPost } from "../client.js";
2
+ export const webhookTools = [
3
+ {
4
+ name: "list_webhooks",
5
+ description: "List all webhook endpoints registered in your Docura organization",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {},
9
+ },
10
+ },
11
+ {
12
+ name: "test_webhook",
13
+ description: "Send a test event to a registered webhook endpoint to verify it is reachable and responding correctly",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ webhookId: { type: "string", description: "Webhook ID to test" },
18
+ },
19
+ required: ["webhookId"],
20
+ },
21
+ },
22
+ ];
23
+ export async function handleWebhookTools(name, args) {
24
+ switch (name) {
25
+ case "list_webhooks": {
26
+ return apiGet("/webhooks");
27
+ }
28
+ case "test_webhook": {
29
+ return apiPost(`/webhooks/${args.webhookId}/test`);
30
+ }
31
+ default:
32
+ throw new Error(`Unknown tool: ${name}`);
33
+ }
34
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "docura-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for the Docura e-signature platform — manage documents from Claude Code, Cursor, or any MCP-compatible AI client",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "docura-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "keywords": [
14
+ "mcp",
15
+ "model-context-protocol",
16
+ "docura",
17
+ "e-signature",
18
+ "esign",
19
+ "ai",
20
+ "claude",
21
+ "cursor"
22
+ ],
23
+ "license": "MIT",
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "start": "node dist/index.js",
27
+ "dev": "tsx index.ts",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.12.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20",
35
+ "tsx": "^4.22.4",
36
+ "typescript": "^5"
37
+ },
38
+ "engines": {
39
+ "node": ">=18"
40
+ }
41
+ }