fastocr-mcp 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,6 @@
1
+ """FastOCR MCP server: OCR local PDFs from any MCP client."""
2
+
3
+ from ._version import __version__
4
+ from .server import create_server, main
5
+
6
+ __all__ = ["__version__", "create_server", "main"]
@@ -0,0 +1,3 @@
1
+ from .server import main
2
+
3
+ main()
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
fastocr_mcp/py.typed ADDED
File without changes
fastocr_mcp/server.py ADDED
@@ -0,0 +1,329 @@
1
+ """MCP server exposing the FastOCR Document OCR API as tools."""
2
+ import json
3
+ import tempfile
4
+ from pathlib import Path
5
+ from typing import Annotated, Any, Callable, Dict, Literal, Optional
6
+
7
+ from fastocr_sdk import (
8
+ AsyncFastOCR,
9
+ AuthenticationError,
10
+ DocumentFailedError,
11
+ DocumentJob,
12
+ FastOCRError,
13
+ NotFoundError,
14
+ PollingTimeoutError,
15
+ RateLimitError,
16
+ )
17
+ from mcp.server.mcpserver import MCPServer
18
+ from mcp.server.mcpserver.exceptions import ToolError
19
+ from mcp.types import ToolAnnotations
20
+ from pydantic import Field
21
+
22
+ from ._version import __version__
23
+
24
+ DOCS_URL = "https://fastocr.org/docs"
25
+ KEYS_URL = "https://fastocr.org/developers/keys"
26
+ BUY_PAGES_URL = f"{KEYS_URL}#buy-api-pages"
27
+ MAX_INLINE_CHARS = 20_000
28
+ # Stay under the 60 s request timeout many MCP clients use; longer documents are resumed with get_document.
29
+ WAIT_SECONDS = 45.0
30
+
31
+ ClientFactory = Callable[[], AsyncFastOCR]
32
+ USER_AGENT = f"fastocr-mcp/{__version__}"
33
+
34
+
35
+ def default_client() -> AsyncFastOCR:
36
+ """An SDK client whose User-Agent names this server, so API access logs can count MCP traffic."""
37
+ client = AsyncFastOCR()
38
+ sdk_agent = client._http.headers.get("User-Agent", "")
39
+ client._http.headers["User-Agent"] = f"{USER_AGENT} {sdk_agent}".strip()
40
+ return client
41
+
42
+ INSTRUCTIONS = f"""\
43
+ FastOCR extracts text from scanned PDFs and images and makes searchable PDFs, in 100+ languages.
44
+ Use ocr_file for a local PDF, PNG, JPG, GIF, WebP or BMP file; pass an absolute path. Every processed
45
+ page (an image is one page) uses one page of the account's FastOCR API allowance, so do not OCR the
46
+ same file twice. The server reads the API key
47
+ from the FASTOCR_API_KEY environment variable. Docs: {DOCS_URL}"""
48
+
49
+ DocumentId = Annotated[str, Field(description="The document id returned by ocr_file or list_documents.")]
50
+ InputPath = Annotated[str, Field(description="Absolute path to a local PDF, PNG, JPG, JPEG, GIF, WebP or BMP file.")]
51
+ OcrOutput = Annotated[
52
+ Literal["text", "pdf"],
53
+ Field(description="'text' returns the extracted text; 'pdf' saves a searchable PDF with a text layer."),
54
+ ]
55
+ OutputPath = Annotated[
56
+ Optional[str],
57
+ Field(description="Optional absolute path for the result file. An existing file there is replaced."),
58
+ ]
59
+
60
+
61
+ def explain(error: FastOCRError) -> str:
62
+ """Turn an SDK error into a message an AI agent or a developer can act on."""
63
+ if isinstance(error, AuthenticationError):
64
+ return (
65
+ "FastOCR did not accept the API key, or none is set. Set FASTOCR_API_KEY in this MCP server's "
66
+ f"environment to a key from {KEYS_URL} (any signed-in account can create one "
67
+ "after accepting the API terms)."
68
+ )
69
+ if error.code == "buy_pages":
70
+ document = f" (document {error.document_id})" if isinstance(error, DocumentFailedError) else ""
71
+ return (
72
+ f"The FastOCR account is out of API pages{document}. Buy pages at {BUY_PAGES_URL}, "
73
+ "then run ocr_file again."
74
+ )
75
+ if error.code == "concurrency_limit":
76
+ return (
77
+ "Too many documents are already processing for this FastOCR account. Wait until one "
78
+ "finishes (check list_documents), then try again. Accounts with purchased API pages can "
79
+ f"process more documents at once: {KEYS_URL}"
80
+ )
81
+ if isinstance(error, RateLimitError):
82
+ wait = int(error.retry_after) if error.retry_after else 60
83
+ return f"FastOCR is rate limiting requests. Try again in {wait} seconds."
84
+ if isinstance(error, NotFoundError):
85
+ return "No document with that id exists in this FastOCR account."
86
+ if error.code == "document_processing":
87
+ return "The document is still processing and cannot be deleted yet. Try again when it has finished."
88
+ if isinstance(error, DocumentFailedError):
89
+ advice = "Trying again may work." if error.retryable else "Trying the same file again will not help."
90
+ return f"OCR failed for document {error.document_id} (code: {error.code}): {error.message} {advice}"
91
+ return f"FastOCR request failed: {error}. See {DOCS_URL}"
92
+
93
+
94
+ def _summary(job: DocumentJob) -> Dict[str, Any]:
95
+ summary: Dict[str, Any] = {
96
+ "document_id": job.id,
97
+ "status": job.status,
98
+ "pages_total": job.pages_total,
99
+ "pages_billed": job.pages_billed,
100
+ "text_available": job.outputs.text_available,
101
+ "pdf_available": job.outputs.pdf_available,
102
+ }
103
+ if job.external_id:
104
+ summary["external_id"] = job.external_id
105
+ if job.error:
106
+ summary["error"] = {"code": job.error.code, "message": job.error.message, "retryable": job.error.retryable}
107
+ if job.is_failed:
108
+ summary["next_step"] = explain(_failure(job))
109
+ return summary
110
+
111
+
112
+ def _partial_note(job: DocumentJob) -> str:
113
+ if not job.is_partial:
114
+ return ""
115
+ return (
116
+ f"\nNote: only {job.pages_billed} of {job.pages_total} pages were processed because the account "
117
+ f"ran out of API pages. Buy pages at {BUY_PAGES_URL} and run ocr_file again to process the whole file."
118
+ )
119
+
120
+
121
+ def _beside(stem: Path, suffix: str) -> Path:
122
+ return stem.with_name(stem.name + suffix)
123
+
124
+
125
+ def _write_text(path: Path, text: str) -> Path:
126
+ path = path.expanduser()
127
+ path.write_text(text, encoding="utf-8")
128
+ return path
129
+
130
+
131
+ async def _deliver(
132
+ client: AsyncFastOCR,
133
+ job: DocumentJob,
134
+ output: str,
135
+ output_path: Optional[str],
136
+ default_stem: Path,
137
+ ) -> str:
138
+ header = f"Document {job.id}: {job.status}, {job.pages_billed} page(s) billed."
139
+ if output == "pdf":
140
+ if not job.outputs.pdf_available:
141
+ raise ToolError(f"Document {job.id} has no searchable PDF. Use output='text' instead.")
142
+ destination = Path(output_path).expanduser() if output_path else _beside(default_stem, ".searchable.pdf")
143
+ await client.documents.download_searchable_pdf(job.id, destination)
144
+ return f"{header}\nSearchable PDF saved to {destination}{_partial_note(job)}"
145
+
146
+ text = await client.documents.get_text(job.id)
147
+ saved = None
148
+ if output_path:
149
+ saved = _write_text(Path(output_path), text)
150
+ elif len(text) > MAX_INLINE_CHARS:
151
+ saved = _write_text(_beside(default_stem, ".ocr.txt"), text)
152
+ if len(text) > MAX_INLINE_CHARS:
153
+ return (
154
+ f"{header}{_partial_note(job)}\nThe text has {len(text)} characters; the first "
155
+ f"{MAX_INLINE_CHARS} are below. The full text is in {saved}.\n\n{text[:MAX_INLINE_CHARS]}"
156
+ )
157
+ where = f"\nFull text saved to {saved}." if saved else ""
158
+ return f"{header}{where}{_partial_note(job)}\n\n{text}"
159
+
160
+
161
+ def _output_file(output_path: Optional[str], source: Optional[Path] = None) -> None:
162
+ if not output_path:
163
+ return
164
+ target = Path(output_path).expanduser()
165
+ if not target.is_absolute():
166
+ raise ToolError(f"output_path must be an absolute path, not '{output_path}'.")
167
+ if source and target.resolve() == source.resolve():
168
+ raise ToolError("output_path must differ from the input file.")
169
+
170
+
171
+ SUPPORTED_SUFFIXES = (".pdf", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp")
172
+
173
+
174
+ def _source(path: str) -> Path:
175
+ source = Path(path).expanduser()
176
+ if not source.is_absolute():
177
+ raise ToolError(f"Pass an absolute path to the file, not '{path}'.")
178
+ if not source.is_file():
179
+ raise ToolError(f"No file at {source}.")
180
+ if source.suffix.lower() not in SUPPORTED_SUFFIXES:
181
+ raise ToolError("FastOCR's API accepts PDF, PNG, JPG, JPEG, GIF, WebP and BMP files.")
182
+ return source
183
+
184
+
185
+ def create_server(client_factory: ClientFactory = default_client) -> MCPServer:
186
+ server = MCPServer(
187
+ "fastocr",
188
+ title="FastOCR",
189
+ instructions=INSTRUCTIONS,
190
+ website_url=DOCS_URL,
191
+ version=__version__,
192
+ )
193
+
194
+ async def run(action: Callable[[AsyncFastOCR], Any]) -> Any:
195
+ try:
196
+ async with client_factory() as client:
197
+ return await action(client)
198
+ except FastOCRError as error:
199
+ raise ToolError(explain(error)) from error
200
+ except OSError as error:
201
+ raise ToolError(f"Could not read or write a local file: {error}") from error
202
+
203
+ async def ocr(path: str, output: str, output_path: Optional[str]) -> str:
204
+ source = _source(path)
205
+ _output_file(output_path, source)
206
+
207
+ async def action(client: AsyncFastOCR) -> str:
208
+ try:
209
+ job = await client.documents.process(source, timeout=WAIT_SECONDS)
210
+ except PollingTimeoutError as pending:
211
+ return (
212
+ f"Document {pending.document_id} is still processing. Call get_document with "
213
+ f"document_id='{pending.document_id}' and output='{output}' to wait for and fetch the result. "
214
+ "Do not run ocr_file on the same file again."
215
+ )
216
+ return await _deliver(client, job, output, output_path, source.with_suffix(""))
217
+
218
+ result: str = await run(action)
219
+ return result
220
+
221
+ @server.tool(annotations=ToolAnnotations(read_only_hint=False, destructive_hint=False, open_world_hint=True))
222
+ async def ocr_file(
223
+ path: InputPath,
224
+ output: OcrOutput = "text",
225
+ output_path: OutputPath = None,
226
+ ) -> str:
227
+ """OCR a local PDF or image with FastOCR and return its text or save a searchable PDF.
228
+
229
+ Uploads the file, runs OCR and waits up to 45 seconds. With output='text' the
230
+ text is returned; text longer than 20,000 characters is cut, and the full text is
231
+ written to output_path or to <file>.ocr.txt next to the input file. With
232
+ output='pdf' the searchable PDF is saved to output_path or to <file>.searchable.pdf
233
+ next to the input file. Either default file replaces an earlier result with the
234
+ same name. Each processed page uses one page of the account's API allowance; an
235
+ image is one page. Large documents take longer: then this returns a document id,
236
+ and get_document with the same output fetches the result. Never call ocr_file
237
+ again for that file, which bills it again.
238
+ """
239
+ return await ocr(path, output, output_path)
240
+
241
+ @server.tool(annotations=ToolAnnotations(read_only_hint=False, destructive_hint=False, open_world_hint=True))
242
+ async def ocr_pdf(
243
+ path: InputPath,
244
+ output: OcrOutput = "text",
245
+ output_path: OutputPath = None,
246
+ ) -> str:
247
+ """The same tool as ocr_file, kept under its earlier name. Prefer ocr_file."""
248
+ return await ocr(path, output, output_path)
249
+
250
+ @server.tool(annotations=ToolAnnotations(read_only_hint=False, destructive_hint=False, idempotent_hint=True, open_world_hint=True))
251
+ async def get_document(
252
+ document_id: DocumentId,
253
+ output: Annotated[
254
+ Literal["status", "text", "pdf"],
255
+ Field(description="'status' (default) returns the status at once; 'text' or 'pdf' waits for the document to finish and fetches the result."),
256
+ ] = "status",
257
+ output_path: OutputPath = None,
258
+ ) -> str:
259
+ """Get a FastOCR document's status, and optionally its text or searchable PDF.
260
+
261
+ Statuses: awaiting_upload, processing, completed, partial (the account ran out of
262
+ pages partway), failed. With output='text' or 'pdf' this waits up to 45 seconds;
263
+ if the document is still processing, call it again. Fetching a result again costs
264
+ no pages. Without output_path, long text and PDFs are saved in the system temp
265
+ directory.
266
+ """
267
+ _output_file(output_path)
268
+
269
+ async def action(client: AsyncFastOCR) -> str:
270
+ if output == "status":
271
+ return _format(_summary(await client.documents.get(document_id)))
272
+ try:
273
+ job = await client.documents.wait_for_completion(document_id, timeout=WAIT_SECONDS)
274
+ except PollingTimeoutError:
275
+ return f"Document {document_id} is still processing. Call get_document again with the same arguments."
276
+ default_stem = Path(tempfile.gettempdir()) / f"fastocr-{job.id}"
277
+ return await _deliver(client, job, output, output_path, default_stem)
278
+
279
+ result: str = await run(action)
280
+ return result
281
+
282
+ @server.tool(annotations=ToolAnnotations(read_only_hint=True, open_world_hint=True))
283
+ async def list_documents(
284
+ limit: Annotated[int, Field(ge=1, le=100, description="How many documents to return, newest first (1 to 100).")] = 20,
285
+ ) -> str:
286
+ """List the most recent documents in the FastOCR account with their status and page counts."""
287
+
288
+ async def action(client: AsyncFastOCR) -> str:
289
+ page = await client.documents.list(limit=limit)
290
+ if not page.documents:
291
+ return "No documents in this FastOCR account yet."
292
+ return "\n".join(_format(_summary(job)) for job in page.documents)
293
+
294
+ result: str = await run(action)
295
+ return result
296
+
297
+ @server.tool(annotations=ToolAnnotations(read_only_hint=False, destructive_hint=True, idempotent_hint=True, open_world_hint=True))
298
+ async def delete_document(document_id: DocumentId) -> str:
299
+ """Permanently delete a FastOCR document and its stored files (original, text, searchable PDF).
300
+
301
+ A document that is still processing cannot be deleted. Local files are not touched.
302
+ """
303
+
304
+ async def action(client: AsyncFastOCR) -> str:
305
+ await client.documents.delete(document_id)
306
+ return f"Document {document_id} is being deleted."
307
+
308
+ result: str = await run(action)
309
+ return result
310
+
311
+ return server
312
+
313
+
314
+ def _failure(job: DocumentJob) -> DocumentFailedError:
315
+ error = job.error
316
+ return DocumentFailedError(
317
+ (error.message if error else "") or "Document processing failed",
318
+ document_id=job.id,
319
+ code=error.code if error else "processing_failed",
320
+ retryable=error.retryable if error else True,
321
+ )
322
+
323
+
324
+ def _format(summary: Dict[str, Any]) -> str:
325
+ return json.dumps({key: value for key, value in summary.items() if value is not None})
326
+
327
+
328
+ def main() -> None:
329
+ create_server().run()
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.5
2
+ Name: fastocr-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for the FastOCR Document OCR API: OCR scanned PDFs into text or searchable PDFs from Claude, Cursor and other MCP clients.
5
+ Project-URL: Homepage, https://fastocr.org
6
+ Project-URL: Documentation, https://fastocr.org/docs
7
+ Project-URL: Repository, https://github.com/fastocr-org/fastocr-mcp
8
+ Project-URL: Changelog, https://github.com/fastocr-org/fastocr-mcp/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/fastocr-org/fastocr-mcp/issues
10
+ Author-email: FastOCR <support@fastocr.org>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: ai-agents,fastocr,mcp,model-context-protocol,ocr,pdf,searchable-pdf
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: fastocr-sdk<0.3,>=0.2.0
28
+ Requires-Dist: mcp<3,>=2.3.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: jsonschema>=4.20.0; extra == 'dev'
31
+ Requires-Dist: mypy>=1.10; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
33
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
34
+ Requires-Dist: respx>=0.21.0; extra == 'dev'
35
+ Requires-Dist: ruff>=0.6; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ <a href="https://fastocr.org"><img src="https://raw.githubusercontent.com/fastocr-org/fastocr-mcp/main/docs/images/logo.png" alt="FastOCR" width="56" height="56"></a>
39
+
40
+ # FastOCR MCP server
41
+
42
+ OCR any scanned PDF or image from Claude, Cursor and other MCP clients, in 100+ languages. **[fastocr.org](https://fastocr.org)** · [Docs](https://fastocr.org/docs) · [Get an API key](https://fastocr.org/developers/keys)
43
+
44
+ <!-- mcp-name: io.github.fastocr-org/fastocr-mcp -->
45
+
46
+ [![PyPI](https://img.shields.io/pypi/v/fastocr-mcp.svg)](https://pypi.org/project/fastocr-mcp/)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
48
+
49
+ An [MCP](https://modelcontextprotocol.io) server for the
50
+ [FastOCR Document OCR API](https://fastocr.org/docs). It lets Claude Desktop,
51
+ Claude Code, Cursor and other MCP clients OCR a local scanned PDF or image and get
52
+ back its text or a searchable PDF.
53
+
54
+ - PDF files up to 1 GiB, and PNG, JPG, JPEG, GIF, WebP and BMP images up to 20 MiB; 100+ languages.
55
+ - Built on the official [`fastocr-sdk`](https://pypi.org/project/fastocr-sdk/) and the
56
+ official MCP Python SDK. Runs locally over stdio; your files go only to the FastOCR API.
57
+
58
+ ![A scanned Arabic book page beside the text FastOCR returned for it](https://raw.githubusercontent.com/fastocr-org/fastocr-mcp/main/docs/images/ocr-before-after.png)
59
+
60
+ A real scanned page and the first lines of the text FastOCR returned for it, unedited.
61
+
62
+ API reference and guides: **https://fastocr.org/docs**
63
+
64
+ ## Get an API key
65
+
66
+ Any signed-in FastOCR account can create an API key and start right away.
67
+
68
+ 1. Sign in and create an API key in the
69
+ [developer console](https://fastocr.org/developers/keys). You accept the
70
+ [API terms](https://fastocr.org/api-terms) with your first key.
71
+ 2. Set the key as `FASTOCR_API_KEY` in your MCP client config (see below).
72
+ 3. When you need more than the free pages, buy a prepaid API page pack by card in the
73
+ [developer console](https://fastocr.org/developers/keys#buy-api-pages).
74
+
75
+ Each account gets 50 free document pages per month.
76
+
77
+ ## Install
78
+
79
+ With [uv](https://docs.astral.sh/uv/) there is nothing to install; MCP clients run it with
80
+ `uvx fastocr-mcp`. Or install it with pip and run `fastocr-mcp`:
81
+
82
+ ```bash
83
+ pip install fastocr-mcp
84
+ ```
85
+
86
+ Python 3.10 or newer. The server reads the key from `FASTOCR_API_KEY`.
87
+
88
+ ## Configure your MCP client
89
+
90
+ ### Claude Code
91
+
92
+ ```bash
93
+ claude mcp add fastocr -e FASTOCR_API_KEY=your_api_key -- uvx fastocr-mcp
94
+ ```
95
+
96
+ ### Claude Desktop
97
+
98
+ Add this to `claude_desktop_config.json` (Settings > Developer > Edit Config), then restart
99
+ Claude Desktop:
100
+
101
+ ```json
102
+ {
103
+ "mcpServers": {
104
+ "fastocr": {
105
+ "command": "uvx",
106
+ "args": ["fastocr-mcp"],
107
+ "env": { "FASTOCR_API_KEY": "your_api_key" }
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### Cursor
114
+
115
+ Add the same block to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project):
116
+
117
+ ```json
118
+ {
119
+ "mcpServers": {
120
+ "fastocr": {
121
+ "command": "uvx",
122
+ "args": ["fastocr-mcp"],
123
+ "env": { "FASTOCR_API_KEY": "your_api_key" }
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ If you installed with pip, use `"command": "fastocr-mcp"` and `"args": []`.
130
+
131
+ ## Tools
132
+
133
+ | Tool | What it does |
134
+ |---|---|
135
+ | `ocr_file(path, output="text", output_path=None)` | Uploads a local PDF or image, runs OCR and waits up to 45 seconds; a larger document returns its id for `get_document`. `output="text"` returns the text (text over 20,000 characters is cut, and the full text is saved to `output_path` or `<file>.ocr.txt` next to the input file). `output="pdf"` saves a searchable PDF to `output_path` or `<file>.searchable.pdf`. |
136
+ | `ocr_pdf(path, output="text", output_path=None)` | The same tool as `ocr_file` under its earlier name, kept so existing setups keep working. |
137
+ | `get_document(document_id, output="status", output_path=None)` | Status and page counts of a document; with `output="text"` or `"pdf"`, waits up to 45 seconds and fetches the finished result. Fetching a result again costs no pages. |
138
+ | `list_documents(limit=20)` | The most recent documents in the account, newest first. |
139
+ | `delete_document(document_id)` | Deletes a document and its stored files on FastOCR. Local files are not touched. |
140
+
141
+ Try asking: *"OCR ~/Downloads/scanned-contract.pdf and summarize the termination clause"*,
142
+ *"Read the text in /Users/me/Desktop/receipt.jpg"* or
143
+ *"Make a searchable copy of /Users/me/archive/1998-letters.pdf"*.
144
+
145
+ Every processed page uses one page of the account's API allowance; an image is one page. A document that runs out
146
+ of pages fails with `buy_pages`; the tool says so and links to the
147
+ [developer console](https://fastocr.org/developers/keys#buy-api-pages).
148
+
149
+ ## Errors
150
+
151
+ Tool errors are written for the agent to act on: a rejected key, an account out of pages
152
+ (`buy_pages`), too many documents processing at once (`concurrency_limit`), a missing file, or an
153
+ unsupported file type. Each message says what to do next. Error codes are listed in the
154
+ [API docs](https://fastocr.org/docs/documents).
155
+
156
+ ## Configuration
157
+
158
+ | Variable | Purpose |
159
+ |---|---|
160
+ | `FASTOCR_API_KEY` | Required. Your API key. |
161
+ | `FASTOCR_BASE_URL` | Optional. Overrides `https://api.fastocr.org`. |
162
+
163
+ ## Links
164
+
165
+ - Docs: https://fastocr.org/docs
166
+ - API keys and page packs: https://fastocr.org/developers/keys
167
+ - Python SDK: https://github.com/fastocr-org/fastocr-python
168
+ - Support: support@fastocr.org
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,10 @@
1
+ fastocr_mcp/__init__.py,sha256=RqiKHUtiXjI4HV8v1qFXj7G9bGgcr4uyLZwKq02UnEw,189
2
+ fastocr_mcp/__main__.py,sha256=3dYKHfmWsrdExFlTFlcR5a_icR9fAkn06Yh14TQkEd8,33
3
+ fastocr_mcp/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
4
+ fastocr_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ fastocr_mcp/server.py,sha256=1XSaCtJfreLiR2aXgzDCRmtAWlSxxPteO1xAVnSMdDs,14169
6
+ fastocr_mcp-0.1.0.dist-info/METADATA,sha256=OwGOXhqrQ1tk03KfuaA8SWb9sHNzpCpxVSk92z1_eVs,7062
7
+ fastocr_mcp-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
8
+ fastocr_mcp-0.1.0.dist-info/entry_points.txt,sha256=IzpVLUD-ltIL-SFZpkECQfOuVErkkqpIufZDxTcy4hE,56
9
+ fastocr_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=iAEJvhOAbbRmyZP6iiDxfCJrifPj_5COWm4eVHmbOQ8,1064
10
+ fastocr_mcp-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fastocr-mcp = fastocr_mcp.server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FastOCR
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.