@agent2pdf/mcp-server 1.0.1 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +95 -5
  2. package/dist/index.js +12 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,15 +1,18 @@
1
1
  # @agent2pdf/mcp-server
2
2
 
3
- Official MCP (Model Context Protocol) Server for Agent2PDF with auto-polling support.
3
+ Official MCP (Model Context Protocol) stdio Server for Agent2PDF with auto-polling support.
4
4
 
5
5
  This MCP Server acts as a bridge between MCP clients (like Claude Desktop, Cursor) and the Agent2PDF async job API. It automatically handles job creation, polling, and returns a direct download URL when the PDF is ready, providing a "pseudo-synchronous" experience.
6
6
 
7
+ > **Note:** Agent2PDF also provides an HTTP MCP endpoint (`POST https://api.agent2pdf.com/api/v1/mcp`) for agents that cannot run a local Node process. The HTTP endpoint returns `job_id` only — agents must poll manually. **This stdio server is recommended** for the best experience.
8
+
7
9
  ## Features
8
10
 
9
- - 🚀 **Auto-polling**: Automatically polls job status until completion
11
+ - 🚀 **Auto-polling**: `generate_pdf` automatically polls job status and returns `download_url` directly
10
12
  - 📦 **Zero-config**: Works out of the box with `npx`
11
- - 🔄 **Three tools**: `generate_pdf`, `list_available_templates`, `get_job_status`
13
+ - 🔄 **Seven tools**: `generate_pdf`, `list_available_templates`, `get_job_status`, `get_branding`, `upload_logo`, `remove_logo`, `submit_feedback`
12
14
  - ⚡ **Fast**: Typical wait time: 5-30 seconds
15
+ - 🔀 **Pseudo-synchronous**: No manual polling needed — just call `generate_pdf` and get the download URL
13
16
 
14
17
  ## Installation
15
18
 
@@ -29,6 +32,8 @@ npm install -g @agent2pdf/mcp-server
29
32
 
30
33
  ## Configuration
31
34
 
35
+ **Note:** The server connects to the **production** API (`https://api.agent2pdf.com/api/v1`) by default. To use the dev environment, set the `AGENT2PDF_API_BASE` environment variable.
36
+
32
37
  ### Claude Desktop
33
38
 
34
39
  Add to your Claude Desktop config file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
@@ -47,6 +52,22 @@ Add to your Claude Desktop config file (`~/Library/Application Support/Claude/cl
47
52
  }
48
53
  ```
49
54
 
55
+ **For dev environment:**
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "agent2pdf": {
60
+ "command": "npx",
61
+ "args": ["@agent2pdf/mcp-server"],
62
+ "env": {
63
+ "AGENT2PDF_API_KEY": "your-dev-api-key",
64
+ "AGENT2PDF_API_BASE": "https://agent2pdf-api-ue3hnvqp6q-de.a.run.app/api/v1"
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
50
71
  Or if installed globally:
51
72
 
52
73
  ```json
@@ -91,7 +112,7 @@ Add to your Cursor settings (`.cursor/mcp.json` or similar):
91
112
 
92
113
  ### `generate_pdf`
93
114
 
94
- Generate a PDF from Markdown content. This tool automatically handles the async job process and returns a download URL when ready.
115
+ Generate a PDF from Markdown content. This tool **automatically handles the async job process** (creates job → polls every 3s → returns download URL when ready). Typical wait: 5–30 seconds.
95
116
 
96
117
  **Arguments:**
97
118
  - `markdown` (required): The document body in Markdown format
@@ -99,6 +120,8 @@ Generate a PDF from Markdown content. This tool automatically handles the async
99
120
  - `title` (optional): Document title. Defaults to "Untitled"
100
121
  - `author` (optional): Author or company name
101
122
  - `template_id` (optional): Custom template ID if using a saved template
123
+ - `use_logo` (optional): Whether to include the company logo in the PDF. Defaults to user settings (true if a logo is uploaded)
124
+ - `heading_numbering` (optional): Heading numbering style. `none` (default), `1.1` (hierarchical, recommended for tech_spec), `1.` (flat, recommended for report), `1.1.1` (deep hierarchical). Manually numbered headings are auto-stripped.
102
125
 
103
126
  **Example:**
104
127
  ```
@@ -128,12 +151,79 @@ Check the status of an async PDF job. Useful for manual polling.
128
151
  Check job status for job_id "abc123"
129
152
  ```
130
153
 
154
+ ### `get_branding`
155
+
156
+ Get current branding settings: company name, logo status, colors, font, and watermark. Use this to check whether a logo is already uploaded before generating a PDF.
157
+
158
+ **Arguments:** None
159
+
160
+ **Example:**
161
+ ```
162
+ Show my current branding settings
163
+ ```
164
+
165
+ ### `upload_logo`
166
+
167
+ Upload or replace the company logo used in PDF headers and cover pages. Provide either a public image URL or a base64-encoded image. PNG/JPG/SVG/WebP, max 2 MB. Min 128×128px, max 2048×2048px. Max aspect ratio 5:1. Recommended: horizontal logo ~600×200px, transparent PNG. The logo will appear on all future PDFs when `use_logo` is enabled (default).
168
+
169
+ **Arguments:**
170
+ - `logo_url` (optional): Public URL of the logo image to download and store
171
+ - `logo_base64` (optional): Base64-encoded image, optionally as a data URI (e.g. `data:image/png;base64,...`)
172
+
173
+ Provide exactly one of `logo_url` or `logo_base64`.
174
+
175
+ **Example:**
176
+ ```
177
+ Upload my company logo from URL "https://example.com/logo.png"
178
+ ```
179
+
180
+ ### `remove_logo`
181
+
182
+ Remove the current company logo. PDFs generated after this will show the company name instead of a logo.
183
+
184
+ **Arguments:** None
185
+
186
+ **Example:**
187
+ ```
188
+ Remove the current logo
189
+ ```
190
+
191
+ ### `submit_feedback`
192
+
193
+ Submit feedback about Agent2PDF service. **Best practice: call this after every PDF generation session** to report quality, suggest improvements, or flag issues. Even a short note helps.
194
+
195
+ **Arguments:**
196
+ - `title` (required): Brief summary of your feedback
197
+ - `category` (optional): `bug`, `feature_request`, `template_request`, `usability`, `general` (default)
198
+ - `rating` (optional): 1–5 satisfaction rating
199
+ - `body` (optional): Detailed description
200
+ - `job_id` (optional): Related job ID if applicable
201
+ - `template_type` (optional): Related template type if applicable
202
+
203
+ **Example:**
204
+ ```
205
+ Submit feedback: title "Great PDF quality", category "general", rating 5, body "The tech_spec template works perfectly for API docs."
206
+ ```
207
+
131
208
  ## How It Works
132
209
 
133
210
  1. **Job Creation**: When `generate_pdf` is called, the server creates an async job via `POST /api/v1/jobs`
134
211
  2. **Auto-polling**: The server polls `GET /api/v1/jobs/{job_id}` every 3 seconds
135
212
  3. **Completion**: When the job status is `completed`, it returns the download URL
136
- 4. **Error Handling**: If the job fails or times out, it returns an error message
213
+ 4. **Error Handling**: If the job fails or times out (~2 min), it returns an error message
214
+ 5. **Branding**: `get_branding`, `upload_logo`, and `remove_logo` manage branding via `GET/POST/DELETE /api/v1/settings/logo`
215
+
216
+ ## stdio Server vs HTTP Endpoint
217
+
218
+ | Aspect | This stdio Server | HTTP Endpoint (`/api/v1/mcp`) |
219
+ |--------|-------------------|-------------------------------|
220
+ | `generate_pdf` returns | `download_url` directly | `job_id` only (manual polling needed) |
221
+ | Polling | Automatic (internal) | Agent calls `get_job_status` in a loop |
222
+ | Requires local Node | Yes (npx or global install) | No |
223
+ | MCP protocol | Full (@modelcontextprotocol/sdk) | Simplified (tools/list + tools/call only) |
224
+ | Best for | Claude Desktop, Cursor, local agents | Cloud agents, serverless environments |
225
+
226
+ For the HTTP endpoint, see: https://agent2pdf.com/docs/api.md
137
227
 
138
228
  ## Development
139
229
 
package/dist/index.js CHANGED
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import{Server as w}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as h}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as E,ListToolsRequestSchema as j}from"@modelcontextprotocol/sdk/types.js";import i,{AxiosError as $}from"axios";var p=process.env.AGENT2PDF_API_BASE||"https://api.agent2pdf.com/api/v1",A=parseInt(process.env.AGENT2PDF_POLLING_INTERVAL_MS||"3000",10),u=parseInt(process.env.AGENT2PDF_MAX_RETRIES||"40",10),g=process.env.AGENT2PDF_API_KEY;g||(console.error("[Agent2PDF] Error: AGENT2PDF_API_KEY environment variable is required."),process.exit(1));var P=()=>g,v=o=>{if(o instanceof $&&o.response?.data){let s=o.response.data;return`[${s.code}] ${s.message}${s.request_id?` (request_id: ${s.request_id})`:""}`}return o instanceof Error?o.message:String(o)},c=new w({name:"agent2pdf-mcp-server",version:"1.0.0"},{capabilities:{tools:{}}});c.setRequestHandler(j,async()=>({tools:[{name:"generate_pdf",description:"Generate a PDF from Markdown. This tool handles the async job process internally and returns a direct download URL when ready. Typical wait time: 5-30 seconds.",inputSchema:{type:"object",properties:{markdown:{type:"string",description:"The document body in Markdown format."},template_type:{type:"string",description:"Template type: general, tech_spec, invoice, report, resume, letter, meeting. Defaults to general.",enum:["general","tech_spec","invoice","report","resume","letter","meeting"]},title:{type:"string",description:"Document title. Defaults to 'Untitled'."},author:{type:"string",description:"Author or company name (optional)."},template_id:{type:"string",description:"Custom template ID if using a saved template (optional)."}},required:["markdown"]}},{name:"list_available_templates",description:"Get a list of available built-in PDF templates with descriptions and recommended use cases.",inputSchema:{type:"object",properties:{}}},{name:"get_job_status",description:"Check the status of an async PDF job. Use this if you want to manually poll a job.",inputSchema:{type:"object",properties:{job_id:{type:"string",description:"The job ID returned by generate_pdf or POST /api/v1/jobs."}},required:["job_id"]}}]}));c.setRequestHandler(E,async o=>{let s=P(),{name:a,arguments:r}=o.params;try{if(a==="list_available_templates")return{content:[{type:"text",text:`Available Templates:
2
+ import{createRequire as h}from"module";import{Server as w}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as P}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as j,ListToolsRequestSchema as $}from"@modelcontextprotocol/sdk/types.js";import p,{AxiosError as R}from"axios";var E=h(import.meta.url),A=E("../package.json"),k=A.version,l=process.env.AGENT2PDF_API_BASE||"https://api.agent2pdf.com/api/v1",v=parseInt(process.env.AGENT2PDF_POLLING_INTERVAL_MS||"3000",10),u=parseInt(process.env.AGENT2PDF_MAX_RETRIES||"40",10),y=process.env.AGENT2PDF_API_KEY;y||(console.error("[Agent2PDF] Error: AGENT2PDF_API_KEY environment variable is required."),process.exit(1));var x=()=>y,D=a=>{if(a instanceof R&&a.response?.data){let s=a.response.data;return`[${s.code}] ${s.message}${s.request_id?` (request_id: ${s.request_id})`:""}`}return a instanceof Error?a.message:String(a)},g=new w({name:"agent2pdf-mcp-server",version:k},{capabilities:{tools:{}}});g.setRequestHandler($,async()=>({tools:[{name:"generate_pdf",description:"Generate a PDF from Markdown. This tool handles the async job process internally and returns a direct download URL when ready. Typical wait time: 5-30 seconds.",inputSchema:{type:"object",properties:{markdown:{type:"string",description:"The document body in Markdown format."},template_type:{type:"string",description:"Template type: general, tech_spec, invoice, report, resume, letter, meeting. Defaults to general.",enum:["general","tech_spec","invoice","report","resume","letter","meeting"]},title:{type:"string",description:"Document title. Defaults to 'Untitled'."},author:{type:"string",description:"Author or company name (optional)."},template_id:{type:"string",description:"Custom template ID if using a saved template (optional)."},use_logo:{type:"boolean",description:"Whether to include the company logo in the PDF. Defaults to user settings (true if a logo is uploaded)."}},required:["markdown"]}},{name:"list_available_templates",description:"Get a list of available built-in PDF templates with descriptions and recommended use cases.",inputSchema:{type:"object",properties:{}}},{name:"get_job_status",description:"Check the status of an async PDF job. Use this if you want to manually poll a job.",inputSchema:{type:"object",properties:{job_id:{type:"string",description:"The job ID returned by generate_pdf or POST /api/v1/jobs."}},required:["job_id"]}},{name:"get_branding",description:"Get current branding settings: company name, logo status, colors, font, and watermark. Use this to check whether a logo is already uploaded before generating a PDF.",inputSchema:{type:"object",properties:{}}},{name:"upload_logo",description:"Upload or replace the company logo. Accepts PNG/JPG/SVG/WebP, max 2 MB, 128-2048px per side, max 5:1 aspect ratio. Recommended: horizontal PNG ~600x200px with transparent background. The logo appears in PDF headers and cover pages.",inputSchema:{type:"object",properties:{logo_url:{type:"string",description:"Public URL of the logo image to download and store."},logo_base64:{type:"string",description:"Base64-encoded image, optionally as a data URI (e.g. data:image/png;base64,...)."}}}},{name:"remove_logo",description:"Remove the current company logo. PDFs generated after this will show the company name instead of a logo.",inputSchema:{type:"object",properties:{}}},{name:"submit_feedback",description:"Submit feedback about Agent2PDF service \u2014 report bugs, request features or templates, or share your experience.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["bug","feature_request","template_request","usability","general"],description:"Type of feedback."},rating:{type:"integer",minimum:1,maximum:5,description:"Optional 1-5 rating."},title:{type:"string",description:"Brief summary of your feedback."},body:{type:"string",description:"Detailed description."},job_id:{type:"string",description:"Related job ID if applicable."},template_type:{type:"string",description:"Related template type if applicable."}},required:["title"]}}]}));g.setRequestHandler(j,async a=>{let s=x(),{name:i,arguments:n}=a.params;try{if(i==="list_available_templates")return{content:[{type:"text",text:`Available Templates:
3
3
 
4
- ${(await i.get(`${p}/templates/manifest`)).data.templates.map(t=>`- **${t.id}** (${t.name}): ${t.description}`).join(`
5
- `)}`}]};if(a==="get_job_status"){let n=r.job_id,e=(await i.get(`${p}/jobs/${n}`,{headers:{"X-API-Key":s}})).data,t=`Job Status:
6
- - ID: ${e.job_id}
7
- - Status: ${e.status}`;return e.progress!==void 0&&(t+=`
8
- - Progress: ${e.progress}%`),e.download_url&&(t+=`
9
- - Download URL: ${e.download_url}`),e.expires_at&&(t+=`
10
- - Expires: ${e.expires_at}`),e.error_message&&(t+=`
11
- - Error: ${e.error_message}`),{content:[{type:"text",text:t}]}}if(a==="generate_pdf"){if(!r||typeof r!="object")throw new Error("Invalid arguments: args must be an object");if(!r.markdown||typeof r.markdown!="string"||r.markdown.trim().length===0)throw new Error("markdown is required and cannot be empty");let n=["general","tech_spec","invoice","report","resume","letter","meeting"];if(r.template_type&&typeof r.template_type=="string"&&!n.includes(r.template_type))throw new Error(`Invalid template_type: ${r.template_type}. Valid values: ${n.join(", ")}`);console.error("[Agent2PDF] Submitting job...");let e=(await i.post(`${p}/jobs`,r,{headers:{"X-API-Key":s,"Content-Type":"application/json"}})).data.job_id;console.error(`[Agent2PDF] Job started: ${e}. Polling for completion...`);for(let t=0;t<u;t++){t>0&&await new Promise(y=>setTimeout(y,A));let _=await i.get(`${p}/jobs/${e}`,{headers:{"X-API-Key":s}}),{status:l,download_url:m,error_message:b,progress:f}=_.data;if(console.error(`[Agent2PDF] Poll ${t+1}/${u}: status=${l}, progress=${f??"N/A"}`),l==="completed"&&m)return{content:[{type:"text",text:`PDF Generated Successfully!
4
+ ${(await p.get(`${l}/templates/manifest`)).data.templates.map(o=>`- **${o.id}** (${o.name}): ${o.description}`).join(`
5
+ `)}`}]};if(i==="get_job_status"){let e=n.job_id,t=(await p.get(`${l}/jobs/${e}`,{headers:{"X-API-Key":s}})).data,o=`Job Status:
6
+ - ID: ${t.job_id}
7
+ - Status: ${t.status}`;return t.progress!==void 0&&(o+=`
8
+ - Progress: ${t.progress}%`),t.download_url&&(o+=`
9
+ - Download URL: ${t.download_url}`),t.expires_at&&(o+=`
10
+ - Expires: ${t.expires_at}`),t.error_message&&(o+=`
11
+ - Error: ${t.error_message}`),{content:[{type:"text",text:o}]}}if(i==="generate_pdf"){if(!n||typeof n!="object")throw new Error("Invalid arguments: args must be an object");if(!n.markdown||typeof n.markdown!="string"||n.markdown.trim().length===0)throw new Error("markdown is required and cannot be empty");let e=["general","tech_spec","invoice","report","resume","letter","meeting"];if(n.template_type&&typeof n.template_type=="string"&&!e.includes(n.template_type))throw new Error(`Invalid template_type: ${n.template_type}. Valid values: ${e.join(", ")}`);console.error("[Agent2PDF] Submitting job...");let t=(await p.post(`${l}/jobs`,n,{headers:{"X-API-Key":s,"Content-Type":"application/json"}})).data.job_id;console.error(`[Agent2PDF] Job started: ${t}. Polling for completion...`);for(let o=0;o<u;o++){o>0&&await new Promise(b=>setTimeout(b,v));let d=await p.get(`${l}/jobs/${t}`,{headers:{"X-API-Key":s}}),{status:c,download_url:m,error_message:f,progress:_}=d.data;if(console.error(`[Agent2PDF] Poll ${o+1}/${u}: status=${c}, progress=${_??"N/A"}`),c==="completed"&&m)return{content:[{type:"text",text:`PDF Generated Successfully!
12
12
 
13
13
  Download URL: ${m}
14
14
 
15
- (Note: Link expires in 1 hour)`}]};if(l==="failed")throw new Error(`Job failed: ${b||"Unknown error"}`)}throw new Error(`Timeout: PDF generation took longer than 2 minutes. Job ID: ${e}`)}throw new Error(`Unknown tool: ${a}`)}catch(n){return{content:[{type:"text",text:`Error: ${v(n)}`}],isError:!0}}});var T=new h;await c.connect(T);console.error("[Agent2PDF] MCP Server started.");
15
+ (Note: Link expires in 1 hour)`}]};if(c==="failed")throw new Error(`Job failed: ${f||"Unknown error"}`)}throw new Error(`Timeout: PDF generation took longer than 2 minutes. Job ID: ${t}`)}if(i==="get_branding"){let r=(await p.get(`${l}/settings`,{headers:{"X-API-Key":s}})).data,t=r.logoUrl?`uploaded (${r.logoUrl})`:"not uploaded";return{content:[{type:"text",text:["Branding settings:",`- company_name: ${r.companyName}`,`- logo: ${t}`,`- primary_color: ${r.primaryColor}`,`- secondary_color: ${r.secondaryColor}`,`- font_family: ${r.fontFamily}`,`- watermark_text: ${r.watermarkText||"(none)"}`].join(`
16
+ `)}]}}if(i==="upload_logo"){let e=n?.logo_url,r=n?.logo_base64;if(!e&&!r)throw new Error("Provide either 'logo_url' or 'logo_base64'.");if(e&&r)throw new Error("Provide only one of 'logo_url' or 'logo_base64', not both.");let t={};return e&&(t.logo_url=e),r&&(t.logo_base64=r),{content:[{type:"text",text:["Logo uploaded successfully.",`- logo_url: ${(await p.post(`${l}/settings/logo`,t,{headers:{"X-API-Key":s,"Content-Type":"application/json"}})).data.logoUrl}`,"","The logo will appear on future PDFs. Use generate_pdf with use_logo=true (default) to include it."].join(`
17
+ `)}]}}if(i==="remove_logo")return await p.delete(`${l}/settings/logo`,{headers:{"X-API-Key":s}}),{content:[{type:"text",text:"Logo removed successfully. PDFs will now show the company name instead of a logo."}]};if(i==="submit_feedback"){let e=n,r=typeof e?.title=="string"?e.title.trim():"";if(!r)throw new Error("title is required for submit_feedback.");let t=typeof e?.category=="string"&&["bug","feature_request","template_request","usability","general"].includes(e.category)?e.category:"general",o=typeof e?.rating=="number"&&e.rating>=1&&e.rating<=5?e.rating:void 0,d={category:t,title:r,body:typeof e?.body=="string"?e.body:void 0,job_id:typeof e?.job_id=="string"?e.job_id:void 0,template_type:typeof e?.template_type=="string"?e.template_type:void 0};return o!==void 0&&(d.rating=o),{content:[{type:"text",text:`Feedback submitted successfully. feedback_id: ${(await p.post(`${l}/feedback`,d,{headers:{"X-API-Key":s,"Content-Type":"application/json"}})).data.feedback_id}`}]}}throw new Error(`Unknown tool: ${i}`)}catch(e){return{content:[{type:"text",text:`Error: ${D(e)}`}],isError:!0}}});var T=new P;await g.connect(T);console.error("[Agent2PDF] MCP Server started.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent2pdf/mcp-server",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Official MCP Server for Agent2PDF with auto-polling support",
5
5
  "type": "module",
6
6
  "bin": {