pdfco-mcp 0.0.4__tar.gz → 0.0.5__tar.gz

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 (37) hide show
  1. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/.github/workflows/release.yml +2 -3
  2. pdfco_mcp-0.0.5/.pre-commit-config.yaml +6 -0
  3. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/PKG-INFO +4 -4
  4. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/README.md +1 -1
  5. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pdfco/mcp/__init__.py +19 -3
  6. pdfco_mcp-0.0.5/pdfco/mcp/models.py +159 -0
  7. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pdfco/mcp/services/client.py +14 -12
  8. pdfco_mcp-0.0.5/pdfco/mcp/services/pdf.py +153 -0
  9. pdfco_mcp-0.0.5/pdfco/mcp/tools/apis/conversion.py +1167 -0
  10. pdfco_mcp-0.0.5/pdfco/mcp/tools/apis/document.py +40 -0
  11. pdfco_mcp-0.0.5/pdfco/mcp/tools/apis/editing.py +102 -0
  12. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pdfco/mcp/tools/apis/extraction.py +21 -7
  13. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pdfco/mcp/tools/apis/file.py +10 -6
  14. pdfco_mcp-0.0.5/pdfco/mcp/tools/apis/form.py +148 -0
  15. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pdfco/mcp/tools/apis/job.py +22 -7
  16. pdfco_mcp-0.0.5/pdfco/mcp/tools/apis/modification.py +82 -0
  17. pdfco_mcp-0.0.5/pdfco/mcp/tools/apis/search.py +98 -0
  18. pdfco_mcp-0.0.5/pdfco/mcp/tools/apis/searchable.py +99 -0
  19. pdfco_mcp-0.0.5/pdfco/mcp/tools/apis/security.py +158 -0
  20. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pyproject.toml +14 -3
  21. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/uv.lock +166 -1
  22. pdfco_mcp-0.0.4/pdfco/mcp/models.py +0 -68
  23. pdfco_mcp-0.0.4/pdfco/mcp/services/pdf.py +0 -95
  24. pdfco_mcp-0.0.4/pdfco/mcp/tools/apis/conversion.py +0 -390
  25. pdfco_mcp-0.0.4/pdfco/mcp/tools/apis/document.py +0 -26
  26. pdfco_mcp-0.0.4/pdfco/mcp/tools/apis/editing.py +0 -72
  27. pdfco_mcp-0.0.4/pdfco/mcp/tools/apis/form.py +0 -105
  28. pdfco_mcp-0.0.4/pdfco/mcp/tools/apis/modification.py +0 -45
  29. pdfco_mcp-0.0.4/pdfco/mcp/tools/apis/search.py +0 -56
  30. pdfco_mcp-0.0.4/pdfco/mcp/tools/apis/searchable.py +0 -60
  31. pdfco_mcp-0.0.4/pdfco/mcp/tools/apis/security.py +0 -98
  32. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/.gitignore +0 -0
  33. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/.python-version +0 -0
  34. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/LICENSE +0 -0
  35. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pdfco/mcp/server.py +0 -0
  36. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pdfco/mcp/services/__init__.py +0 -0
  37. {pdfco_mcp-0.0.4 → pdfco_mcp-0.0.5}/pdfco/mcp/tools/__init__.py +0 -0
@@ -48,13 +48,12 @@ jobs:
48
48
  uses: astral-sh/setup-uv@v5
49
49
  with:
50
50
  version: 0.7.12
51
- enable-cache: 'true'
52
- cache-suffix: 3.12
51
+ enable-cache: true
53
52
 
54
53
  - name: Install Python dependencies
55
54
  run: uv sync --frozen
56
55
  shell: bash
57
-
56
+
58
57
  - name: Download updated pyproject.toml
59
58
  uses: actions/download-artifact@v4
60
59
  with:
@@ -0,0 +1,6 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.11.13
4
+ hooks:
5
+ - id: ruff
6
+ - id: ruff-format
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfco-mcp
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: MCP server for PDF.Co
5
5
  Project-URL: Homepage, https://pdf.co
6
6
  Project-URL: Documentation, https://developer.pdf.co
7
- Project-URL: Repository, https://github.com/ae38/pdfco-mcp
8
- Project-URL: Issues, https://github.com/ae38/pdfco-mcp/issues
7
+ Project-URL: Repository, https://github.com/pdfdotco/pdfco-mcp
8
+ Project-URL: Issues, https://github.com/pdfdotco/pdfco-mcp/issues
9
9
  Author-email: "PDF.Co" <support@pdf.co>
10
10
  License: MIT License
11
11
 
@@ -43,7 +43,7 @@ Requires-Dist: httpx>=0.28.1
43
43
  Requires-Dist: mcp[cli]>=1.6.0
44
44
  Description-Content-Type: text/markdown
45
45
 
46
- # PDF.co MCP Server
46
+ # PDF.co MCP
47
47
 
48
48
  PDF.co MCP Server provides [PDF.co API](https://pdf.co) functionality through the Model Context Protocol (MCP), enabling AI assistants to easily perform various PDF processing tasks.
49
49
 
@@ -1,4 +1,4 @@
1
- # PDF.co MCP Server
1
+ # PDF.co MCP
2
2
 
3
3
  PDF.co MCP Server provides [PDF.co API](https://pdf.co) functionality through the Model Context Protocol (MCP), enabling AI assistants to easily perform various PDF processing tasks.
4
4
 
@@ -1,7 +1,20 @@
1
1
  import sys
2
2
  from pdfco.mcp.server import mcp
3
- from pdfco.mcp.tools.apis import conversion, job, file, modification, form, search, searchable, security, document, extraction, editing
4
-
3
+ from pdfco.mcp.tools.apis import (
4
+ conversion,
5
+ job,
6
+ file,
7
+ modification,
8
+ form,
9
+ search,
10
+ searchable,
11
+ security,
12
+ document,
13
+ extraction,
14
+ editing,
15
+ )
16
+
17
+
5
18
  def main():
6
19
  if len(sys.argv) > 1:
7
20
  transport = sys.argv[1]
@@ -14,7 +27,9 @@ def main():
14
27
  mcp.run(transport=transport, host="0.0.0.0", port=port)
15
28
  elif transport == "streamable-http":
16
29
  if len(sys.argv) < 3:
17
- raise ValueError("Streamable HTTP transport requires a port number and path")
30
+ raise ValueError(
31
+ "Streamable HTTP transport requires a port number and path"
32
+ )
18
33
  port = int(sys.argv[2])
19
34
  path = sys.argv[3]
20
35
  mcp.run(transport=transport, host="0.0.0.0", port=port, path=path)
@@ -23,5 +38,6 @@ def main():
23
38
  else:
24
39
  mcp.run(transport="stdio")
25
40
 
41
+
26
42
  if __name__ == "__main__":
27
43
  main()
@@ -0,0 +1,159 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import Any
3
+
4
+
5
+ class BaseResponse(BaseModel):
6
+ status: str
7
+ content: Any
8
+ credits_used: int | None = None
9
+ credits_remaining: int | None = None
10
+ tips: str | None = None
11
+
12
+
13
+ class ConversionParams(BaseModel):
14
+ url: str = Field(
15
+ description="URL to the source file. Supports publicly accessible links including Google Drive, Dropbox, PDF.co Built-In Files Storage. Use 'upload_file' tool to upload local files.",
16
+ default="",
17
+ )
18
+ httpusername: str = Field(
19
+ description="HTTP auth user name if required to access source url. (Optional)",
20
+ default="",
21
+ )
22
+ httppassword: str = Field(
23
+ description="HTTP auth password if required to access source url. (Optional)",
24
+ default="",
25
+ )
26
+ pages: str = Field(
27
+ description="Comma-separated page indices (e.g., '0, 1, 2-' or '1, 3-7'). Use '!' for inverted page numbers (e.g., '!0' for last page). Processes all pages if None. (Optional)",
28
+ default="",
29
+ )
30
+ unwrap: bool = Field(
31
+ description="Unwrap lines into a single line within table cells when lineGrouping is enabled. Must be true or false. (Optional)",
32
+ default=False,
33
+ )
34
+ rect: str = Field(
35
+ description="Defines coordinates for extraction (e.g., '51.8,114.8,235.5,204.0'). (Optional)",
36
+ default="",
37
+ )
38
+ lang: str = Field(
39
+ description="Language for OCR for scanned documents. Default is 'eng'. See PDF.co docs for supported languages. (Optional, Default: 'eng')",
40
+ default="eng",
41
+ )
42
+ line_grouping: str = Field(
43
+ description="Enables line grouping within table cells when set to '1'. (Optional)",
44
+ default="0",
45
+ )
46
+ password: str = Field(
47
+ description="Password of the PDF file. (Optional)", default=""
48
+ )
49
+ name: str = Field(
50
+ description="File name for the generated output. (Optional)", default=""
51
+ )
52
+ autosize: bool = Field(
53
+ description="Controls automatic page sizing. If true, page dimensions adjust to content. If false, uses worksheet’s page setup. (Optional)",
54
+ default=False,
55
+ )
56
+
57
+ html: str = Field(
58
+ description="Input HTML code to be converted. To convert the link to a PDF use the /pdf/convert/from/url endpoint instead.",
59
+ default="",
60
+ )
61
+ templateId: str = Field(
62
+ description="Set to the ID of your HTML template. You can find and copy the ID from HTML to PDF Templates.",
63
+ default="",
64
+ )
65
+ templateData: str = Field(
66
+ description="Set it to a string with input JSON data (recommended) or CSV data.",
67
+ default="",
68
+ )
69
+ margins: str = Field(
70
+ description="Set to CSS style margins like 10px, 5mm, 5in for all sides or 5px 5px 5px 5px (the order of margins is top, right, bottom, left). (Optional)",
71
+ default="",
72
+ )
73
+ paperSize: str = Field(
74
+ description="A4 is set by default. Can be Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6 or a custom size. Custom size can be set in px (pixels), mm or in (inches) with width and height separated by space like this: 200 300, 200px 300px, 200mm 300mm, 20cm 30cm or 6in 8in. (Optional)",
75
+ default="",
76
+ )
77
+ orientation: str = Field(
78
+ description="Set to Portrait or Landscape. Portrait is set by default. (Optional)",
79
+ default="",
80
+ )
81
+ printBackground: bool = Field(
82
+ description="true by default. Set to false to disable printing of background. (Optional)",
83
+ default=True,
84
+ )
85
+ mediaType: str = Field(
86
+ description="Uses print by default. Set to screen to convert HTML as it appears in a browser or print to convert as it appears for printing or none to set none as mediaType for CSS styles. (Optional)",
87
+ default="",
88
+ )
89
+ DoNotWaitFullLoad: bool = Field(
90
+ description="false by default. Set to true to skip waiting for full load (like full video load etc. that may affect the total conversion time). (Optional)",
91
+ default=False,
92
+ )
93
+ header: str = Field(
94
+ description="User definable HTML for the header to be applied on every page header. (Optional)",
95
+ default="",
96
+ )
97
+ footer: str = Field(
98
+ description="User definable HTML for the footer to be applied on every page footer. (Optional)",
99
+ default="",
100
+ )
101
+
102
+ worksheetIndex: str = Field(
103
+ description="Index of the worksheet to convert. (Optional)", default=""
104
+ )
105
+
106
+ def parse_payload(self, async_mode: bool = True):
107
+ payload = {
108
+ "async": async_mode,
109
+ }
110
+ if self.url:
111
+ payload["url"] = self.url
112
+ if self.httpusername:
113
+ payload["httpusername"] = self.httpusername
114
+ if self.httppassword:
115
+ payload["httppassword"] = self.httppassword
116
+ if self.pages:
117
+ payload["pages"] = self.pages
118
+ if self.unwrap:
119
+ payload["unwrap"] = self.unwrap
120
+ if self.rect:
121
+ payload["rect"] = self.rect
122
+ if self.lang:
123
+ payload["lang"] = self.lang
124
+ if self.line_grouping:
125
+ payload["lineGrouping"] = self.line_grouping
126
+ if self.password:
127
+ payload["password"] = self.password
128
+ if self.name:
129
+ payload["name"] = self.name
130
+ if self.autosize:
131
+ payload["autosize"] = self.autosize
132
+
133
+ if self.html:
134
+ payload["html"] = self.html
135
+ if self.templateId:
136
+ payload["templateId"] = self.templateId
137
+ if self.templateData:
138
+ payload["templateData"] = self.templateData
139
+ if self.margins:
140
+ payload["margins"] = self.margins
141
+ if self.paperSize:
142
+ payload["paperSize"] = self.paperSize
143
+ if self.orientation:
144
+ payload["orientation"] = self.orientation
145
+ if self.printBackground:
146
+ payload["printBackground"] = self.printBackground
147
+ if self.mediaType:
148
+ payload["mediaType"] = self.mediaType
149
+ if self.DoNotWaitFullLoad:
150
+ payload["DoNotWaitFullLoad"] = self.DoNotWaitFullLoad
151
+ if self.header:
152
+ payload["header"] = self.header
153
+ if self.footer:
154
+ payload["footer"] = self.footer
155
+
156
+ if self.worksheetIndex:
157
+ payload["worksheetIndex"] = self.worksheetIndex
158
+
159
+ return payload
@@ -1,20 +1,22 @@
1
1
  from contextlib import asynccontextmanager
2
2
  from httpx import AsyncClient
3
- import os, sys
4
- from typing import AsyncGenerator, Optional
3
+ import os
4
+ import sys
5
+ from typing import AsyncGenerator
5
6
  import importlib.metadata
6
7
 
7
8
  __BASE_URL = "https://api.pdf.co"
8
- X_API_KEY = os.getenv("X_API_KEY")
9
+ X_API_KEY = os.getenv("X_API_KEY")
9
10
 
10
- __version__ = importlib.metadata.version('pdfco-mcp')
11
+ __version__ = importlib.metadata.version("pdfco-mcp")
11
12
  print(f"pdfco-mcp version: {__version__}", file=sys.stderr)
12
13
 
14
+
13
15
  @asynccontextmanager
14
16
  async def PDFCoClient(api_key: str = None) -> AsyncGenerator[AsyncClient, None]:
15
17
  # Use provided API key, fall back to environment variable
16
18
  x_api_key = api_key or X_API_KEY
17
-
19
+
18
20
  if not x_api_key:
19
21
  raise ValueError("""API key is required. Please provide an API key as a parameter or set X_API_KEY in the environment variables.
20
22
 
@@ -42,14 +44,14 @@ async def PDFCoClient(api_key: str = None) -> AsyncGenerator[AsyncClient, None]:
42
44
 
43
45
  Or provide the API key as a parameter when calling the tool.
44
46
  """)
45
-
47
+
46
48
  client = AsyncClient(
47
- base_url=__BASE_URL,
48
- headers={
49
- "x-api-key": x_api_key,
50
- "User-Agent": f"pdfco-mcp/{__version__}",
51
- },
52
- )
49
+ base_url=__BASE_URL,
50
+ headers={
51
+ "x-api-key": x_api_key,
52
+ "User-Agent": f"pdfco-mcp/{__version__}",
53
+ },
54
+ )
53
55
  try:
54
56
  yield client
55
57
  finally:
@@ -0,0 +1,153 @@
1
+ import sys
2
+ from pdfco.mcp.models import BaseResponse, ConversionParams
3
+ from pdfco.mcp.services.client import PDFCoClient
4
+
5
+
6
+ async def convert_to(
7
+ _from: str, _to: str, params: ConversionParams, api_key: str = None
8
+ ) -> BaseResponse:
9
+ return await request(f"{_from}/convert/to/{_to}", params, api_key=api_key)
10
+
11
+
12
+ async def convert_from(
13
+ _to: str, _from: str, params: ConversionParams, api_key: str = None
14
+ ) -> BaseResponse:
15
+ return await request(f"{_to}/convert/from/{_from}", params, api_key=api_key)
16
+
17
+
18
+ async def merge_pdf(params: ConversionParams, api_key: str = None) -> BaseResponse:
19
+ return await request("pdf/merge2", params, api_key=api_key)
20
+
21
+
22
+ async def split_pdf(params: ConversionParams, api_key: str = None) -> BaseResponse:
23
+ return await request("pdf/split", params, api_key=api_key)
24
+
25
+
26
+ async def get_pdf_form_fields_info(
27
+ params: ConversionParams, api_key: str = None
28
+ ) -> BaseResponse:
29
+ return await request("pdf/info/fields", params, api_key=api_key)
30
+
31
+
32
+ async def fill_pdf_form_fields(
33
+ params: ConversionParams,
34
+ fields: list = None,
35
+ annotations: list = None,
36
+ api_key: str = None,
37
+ ) -> BaseResponse:
38
+ custom_payload = {}
39
+ if fields:
40
+ custom_payload["fields"] = fields
41
+ if annotations:
42
+ custom_payload["annotations"] = annotations
43
+ return await request(
44
+ "pdf/edit/add", params, custom_payload=custom_payload, api_key=api_key
45
+ )
46
+
47
+
48
+ async def pdf_add(
49
+ params: ConversionParams, api_key: str = None, **kwargs
50
+ ) -> BaseResponse:
51
+ """General PDF Add function that supports all PDF Add API parameters"""
52
+ custom_payload = {}
53
+
54
+ # Add all supported parameters
55
+ for key, value in kwargs.items():
56
+ if value is not None and value != "":
57
+ custom_payload[key] = value
58
+
59
+ return await request(
60
+ "pdf/edit/add", params, custom_payload=custom_payload, api_key=api_key
61
+ )
62
+
63
+
64
+ async def find_text_in_pdf(
65
+ params: ConversionParams,
66
+ search_string: str,
67
+ regex_search: bool = False,
68
+ word_matching_mode: str = None,
69
+ api_key: str = None,
70
+ ) -> BaseResponse:
71
+ custom_payload = {"searchString": search_string, "regexSearch": regex_search}
72
+ if word_matching_mode:
73
+ custom_payload["wordMatchingMode"] = word_matching_mode
74
+ return await request(
75
+ "pdf/find", params, custom_payload=custom_payload, api_key=api_key
76
+ )
77
+
78
+
79
+ async def find_table_in_pdf(
80
+ params: ConversionParams, api_key: str = None
81
+ ) -> BaseResponse:
82
+ return await request("pdf/find/table", params, api_key=api_key)
83
+
84
+
85
+ async def make_pdf_searchable(
86
+ params: ConversionParams, api_key: str = None
87
+ ) -> BaseResponse:
88
+ return await request("pdf/makesearchable", params, api_key=api_key)
89
+
90
+
91
+ async def make_pdf_unsearchable(
92
+ params: ConversionParams, api_key: str = None
93
+ ) -> BaseResponse:
94
+ return await request("pdf/makeunsearchable", params, api_key=api_key)
95
+
96
+
97
+ async def get_pdf_info(params: ConversionParams, api_key: str = None) -> BaseResponse:
98
+ return await request("pdf/info", params, api_key=api_key)
99
+
100
+
101
+ async def add_pdf_password(
102
+ params: ConversionParams, api_key: str = None, **kwargs
103
+ ) -> BaseResponse:
104
+ return await request(
105
+ "pdf/security/add", params, custom_payload=kwargs, api_key=api_key
106
+ )
107
+
108
+
109
+ async def remove_pdf_password(
110
+ params: ConversionParams, api_key: str = None
111
+ ) -> BaseResponse:
112
+ return await request("pdf/security/remove", params, api_key=api_key)
113
+
114
+
115
+ async def parse_invoice(params: ConversionParams, api_key: str = None) -> BaseResponse:
116
+ return await request("ai-invoice-parser", params, api_key=api_key)
117
+
118
+
119
+ async def extract_pdf_attachments(
120
+ params: ConversionParams, api_key: str = None
121
+ ) -> BaseResponse:
122
+ return await request("pdf/attachments/extract", params, api_key=api_key)
123
+
124
+
125
+ async def request(
126
+ endpoint: str,
127
+ params: ConversionParams,
128
+ custom_payload: dict = None,
129
+ api_key: str = None,
130
+ ) -> BaseResponse:
131
+ payload = params.parse_payload(async_mode=True)
132
+ if custom_payload:
133
+ payload.update(custom_payload)
134
+
135
+ try:
136
+ async with PDFCoClient(api_key=api_key) as client:
137
+ url = f"/v1/{endpoint}"
138
+ print(f"Requesting {url} with payload {payload}", file=sys.stderr)
139
+ response = await client.post(url, json=payload)
140
+ print(f"response: {response}", file=sys.stderr)
141
+ json_data = response.json()
142
+ return BaseResponse(
143
+ status="working",
144
+ content=json_data,
145
+ credits_used=json_data.get("credits"),
146
+ credits_remaining=json_data.get("remainingCredits"),
147
+ tips=f"You **should** use the 'wait_job_completion' tool to wait for the job [{json_data.get('jobId')}] to complete if a jobId is present.",
148
+ )
149
+ except Exception as e:
150
+ return BaseResponse(
151
+ status="error",
152
+ content=f"{type(e)}: {[arg for arg in e.args if arg]}",
153
+ )