parseur.mcp_server¶
Model Context Protocol (MCP) server for Parseur.
Exposes the parseur-py library as MCP tools so AI assistants (Claude Desktop, Cursor, etc.) can manage Parseur mailboxes, parser fields, documents, exports, and webhooks.
Every tool carries a human-readable title, a description, per-argument descriptions, behavioral annotations (read-only / destructive / idempotent / open-world hints), and structured JSON output so the assistant can reason about what each command does and what it returns.
Requires the mcp extra:
pip install "parseur-py[mcp]"
Run it with:
parseur mcp
or directly:
python -m parseur.mcp_server
Attributes¶
Classes¶
FastMCP that fills in its instructions lazily, once all tools exist. |
Functions¶
Allow overriding the configured API key/base via environment variables. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Entry point for the |
Module Contents¶
- class parseur.mcp_server.ParseurMCP(*args: Any, **kwargs: Any)[source]¶
Bases:
mcp.server.fastmcp.FastMCPFastMCP that fills in its instructions lazily, once all tools exist.
instructionsis a construction-time argument in FastMCP, but ours is an f-string referencing each tool’s__name__— it can only be built after the tool functions are defined, and those needmcpto exist first. So instead of passing the text up front, we inject it when the initialization options are built (the single point every transport goes through, just before the handshake), by which timeINSTRUCTIONSis defined.
- parseur.mcp_server.ensure_api_key()[source]¶
Allow overriding the configured API key/base via environment variables.
MCP clients usually inject credentials through the server’s environment, so
PARSEUR_API_KEY(and optionallyPARSEUR_API_BASE) take precedence over the values loaded from~/.parseur.conf.
- parseur.mcp_server.list_mailboxes(search: Annotated[Optional[str], Field(description='Filter by mailbox name or email prefix.')] = None, order_by: Annotated[Optional[str], Field(description='Sort field. One of: name, document_count, template_count, PARSEDOK_count, PARSEDKO_count, QUOTAEXC_count, EXPORTKO_count.')] = None, descending: Annotated[bool, Field(description='Sort in descending order (default is ascending).')] = False) List[Dict[str, Any]][source]¶
- parseur.mcp_server.AI_ENGINE_DESC = 'AI engine for the mailbox. One of: . Defaults to GCP_AI_2 (AI Vision engine) when omitted.'[source]¶
- parseur.mcp_server.create_mailbox(name: Annotated[Optional[str], Field(description='Title of the mailbox. Parseur generates one if omitted.')] = None, ai_engine: Annotated[Optional[str], Field(description=AI_ENGINE_DESC)] = None, ai_instructions: Annotated[Optional[str], Field(description='Optional natural-language extraction instructions.')] = None) Dict[str, Any][source]¶
- parseur.mcp_server.rename_mailbox(mailbox_id: MailboxId, name: Annotated[str, Field(description='New display name for the mailbox.')]) Dict[str, Any][source]¶
- parseur.mcp_server.set_ai_engine(mailbox_id: MailboxId, ai_engine: Annotated[str, Field(description=AI_ENGINE_DESC)]) Dict[str, Any][source]¶
- parseur.mcp_server.set_ai_instructions(mailbox_id: MailboxId, instructions: Annotated[Optional[str], Field(description='Extraction instructions, or null to clear them.')] = None) Dict[str, Any][source]¶
- parseur.mcp_server.DATE_FORMAT_DESC = 'How ambiguous dates in documents are read. One of: ; or null to auto-detect.'[source]¶
- parseur.mcp_server.DECIMAL_SEPARATOR_DESC = 'Decimal separator for numbers in documents. One of: ; or null to clear.'[source]¶
- parseur.mcp_server.PAGE_RANGES_DESC = 'Page ranges, e.g. [{"start_index": 1, "end_index": 5}, {"start_index": 6, "end_index": null}]...[source]¶
- parseur.mcp_server.set_email_processing(mailbox_id: MailboxId, mode: Annotated[str, Field(description=EMAIL_PROCESSING_DESC)]) Dict[str, Any][source]¶
- parseur.mcp_server.set_metadata(mailbox_id: MailboxId, enable: Annotated[List[str], Field(description='Columns to enable. ' + METADATA_COLUMNS_DESC)] = [], disable: Annotated[List[str], Field(description='Columns to disable. ' + METADATA_COLUMNS_DESC)] = []) Dict[str, Any][source]¶
- parseur.mcp_server.set_timezone(mailbox_id: MailboxId, timezone: Annotated[Optional[str], Field(description="IANA timezone (e.g. 'Europe/Paris'), or null to clear.")] = None) Dict[str, Any][source]¶
- parseur.mcp_server.set_date_format(mailbox_id: MailboxId, date_format: Annotated[Optional[str], Field(description=DATE_FORMAT_DESC)] = None) Dict[str, Any][source]¶
- parseur.mcp_server.set_decimal_separator(mailbox_id: MailboxId, separator: Annotated[Optional[str], Field(description=DECIMAL_SEPARATOR_DESC)] = None) Dict[str, Any][source]¶
- parseur.mcp_server.set_allowed_extensions(mailbox_id: MailboxId, extensions: Annotated[Optional[List[str]], Field(description="Extensions to accept, e.g. ['pdf', 'docx', 'png']. Supported: " + ', '.join(sorted(parseur.SUPPORTED_FILE_EXTENSIONS)) + '.')] = None) Dict[str, Any][source]¶
- parseur.mcp_server.set_sender_filter(mailbox_id: MailboxId, mode: Annotated[str, Field(description=SENDER_FILTER_DESC)], emails_or_domains: Annotated[List[str], Field(description="Emails or domains to allow/block, e.g. ['acme.com'].")]) Dict[str, Any][source]¶
- parseur.mcp_server.split_by_ai(mailbox_id: MailboxId, instructions: Annotated[Optional[str], Field(description='Optional AI splitting instructions.')] = None, enabled: Annotated[bool, Field(description='Set to false to turn AI splitting off.')] = True) Dict[str, Any][source]¶
- parseur.mcp_server.split_by_page(mailbox_id: MailboxId, every: Annotated[Optional[int], Field(description='Pages per resulting document (required unless disabling).')] = None, enabled: Annotated[bool, Field(description='Set to false to turn this split method off.')] = True) Dict[str, Any][source]¶
- parseur.mcp_server.split_by_page_range(mailbox_id: MailboxId, ranges: Annotated[Optional[List[Dict[str, Any]]], Field(description=PAGE_RANGES_DESC + ' Required unless disabling.')] = None, enabled: Annotated[bool, Field(description='Set to false to clear this split method.')] = True) Dict[str, Any][source]¶
- parseur.mcp_server.split_by_keywords(mailbox_id: MailboxId, keywords: Annotated[Optional[List[Dict[str, Any]]], Field(description='Keywords, e.g. [{"keyword": "Invoice", "is_before": true}] (is_before splits before vs after the keyword). Required unless disabling.')] = None, enabled: Annotated[bool, Field(description='Set to false to clear this split method.')] = True) Dict[str, Any][source]¶
- parseur.mcp_server.process_page_range(mailbox_id: MailboxId, ranges: Annotated[Optional[List[Dict[str, Any]]], Field(description=PAGE_RANGES_DESC + ' Required unless disabling.')] = None, enabled: Annotated[bool, Field(description='Set to false to process every page again.')] = True) Dict[str, Any][source]¶
- parseur.mcp_server.process_odd_pages(mailbox_id: MailboxId, enabled: Annotated[bool, Field(description='Set to false to process all pages again.')] = True) Dict[str, Any][source]¶
- parseur.mcp_server.process_even_pages(mailbox_id: MailboxId, enabled: Annotated[bool, Field(description='Set to false to process all pages again.')] = True) Dict[str, Any][source]¶
- parseur.mcp_server.add_parser_field(mailbox_id: MailboxId, name: Annotated[str, Field(description='Name of the new field.')], field_format: Annotated[str, Field(description=FIELD_FORMAT_DESC)], query: Annotated[Optional[str], Field(description='Optional AI extraction instructions for the field.')] = None, is_required: Annotated[Optional[bool], Field(description='Whether the field is required.')] = None, choice_set: Annotated[Optional[List[str]], Field(description='Optional list of allowed values for the field.')] = None) List[Dict[str, Any]][source]¶
- parseur.mcp_server.update_parser_field(mailbox_id: MailboxId, field_id: FieldId, name: Annotated[Optional[str], Field(description='New name for the field.')] = None, field_format: Annotated[Optional[str], Field(description=FIELD_FORMAT_DESC)] = None, query: Annotated[Optional[str], Field(description='New AI extraction instructions.')] = None, is_required: Annotated[Optional[bool], Field(description='Whether the field is required.')] = None, choice_set: Annotated[Optional[List[str]], Field(description='New list of allowed values for the field.')] = None) List[Dict[str, Any]][source]¶
- parseur.mcp_server.delete_parser_field(mailbox_id: MailboxId, field_id: FieldId) List[Dict[str, Any]][source]¶
- parseur.mcp_server.list_documents(mailbox_id: MailboxId, search: Annotated[Optional[str], Field(description='Match document id, name, template name, from/to/cc/bcc email addresses, or document metadata.')] = None, order_by: Annotated[Optional[str], Field(description='Sort field. One of: name, created, processed, status.')] = None, descending: Annotated[bool, Field(description='Sort in descending order (default is ascending).')] = False, received_after: Annotated[Optional[str], Field(description='Only documents received on/after this date (YYYY-MM-DD).')] = None, received_before: Annotated[Optional[str], Field(description='Only documents received on/before this date (YYYY-MM-DD).')] = None, with_result: Annotated[bool, Field(description='Include the parsed result of each document.')] = False) List[Dict[str, Any]][source]¶
- parseur.mcp_server.copy_document(document_id: DocumentId, target_mailbox_id: Annotated[int, Field(description='The mailbox id to copy the document into.')]) Dict[str, Any][source]¶
- parseur.mcp_server.FILE_PATH_DESC = 'Absolute path to the document on the machine running this MCP server (when launched locally by...[source]¶
- parseur.mcp_server.upload_file(mailbox_id: MailboxId, file_path: Annotated[str, Field(description=FILE_PATH_DESC)]) Dict[str, Any][source]¶
- parseur.mcp_server.upload_text(recipient: Annotated[str, Field(description='The destination mailbox email address.')], subject: Annotated[str, Field(description='Subject line of the document.')], sender: Annotated[Optional[str], Field(description='Optional sender email address.')] = None, body_html: Annotated[Optional[str], Field(description='Optional HTML body.')] = None, body_plain: Annotated[Optional[str], Field(description='Optional plain-text body.')] = None) Dict[str, Any][source]¶
- parseur.mcp_server.WAIT_DESC_SUFFIX = ' Polls every 5 seconds for up to 10 minutes; errors if the document is still being processed...[source]¶
- parseur.mcp_server.upload_file_and_wait(mailbox_id: MailboxId, file_path: Annotated[str, Field(description=FILE_PATH_DESC)]) Dict[str, Any][source]¶
- parseur.mcp_server.upload_text_and_wait(recipient: Annotated[str, Field(description='The destination mailbox email address.')], subject: Annotated[str, Field(description='Subject line of the document.')], sender: Annotated[Optional[str], Field(description='Optional sender email address.')] = None, body_html: Annotated[Optional[str], Field(description='Optional HTML body.')] = None, body_plain: Annotated[Optional[str], Field(description='Optional plain-text body.')] = None) Dict[str, Any][source]¶
- parseur.mcp_server.create_webhook(event: Annotated[str, Field(description='Event type. One of: document.processed, document.processed.flattened, document.template_needed, document.export_failed, table.processed, table.processed.flattened.')], target_url: Annotated[str, Field(description='URL that will receive the webhook POST requests.')], mailbox_id: Annotated[Optional[int], Field(description='Mailbox id (required for document.* events).')] = None, table_field_id: Annotated[Optional[str], Field(description='Table field id like "PF12345" (required for table.* events).')] = None, headers: Annotated[Optional[Dict[str, str]], Field(description='Optional custom HTTP headers to send with each POST.')] = None, name: Annotated[Optional[str], Field(description='Optional name for the webhook.')] = None) Dict[str, Any][source]¶
- parseur.mcp_server.enable_webhook(mailbox_id: MailboxId, webhook_id: WebhookId) Dict[str, Any][source]¶
- parseur.mcp_server.pause_webhook(mailbox_id: MailboxId, webhook_id: WebhookId) Dict[str, Any][source]¶
- parseur.mcp_server.EXPORT_TYPE_DESC = "Export type. PARSER exports the document-level result; PARSER_FIELD exports a table field's...[source]¶
- parseur.mcp_server.ITEMS_DESC = 'Columns to export. Use list_export_fields to discover the valid items.'[source]¶
- parseur.mcp_server.get_table_export(mailbox_id: MailboxId, field_id: FieldId) Dict[str, Any][source]¶
- parseur.mcp_server.get_export_config(mailbox_id: MailboxId, export_config_id: ExportConfigId) Dict[str, Any][source]¶
- parseur.mcp_server.create_export_config(mailbox_id: MailboxId, name: Annotated[str, Field(description='Name of the export configuration.')], items: Annotated[List[str], Field(description=ITEMS_DESC)], export_type: Annotated[str, Field(description=EXPORT_TYPE_DESC)] = 'PARSER', parser_field_id: Annotated[Optional[str], Field(description='Table field id (required for PARSER_FIELD), e.g. "PF1".')] = None) Dict[str, Any][source]¶
- parseur.mcp_server.update_export_config(mailbox_id: MailboxId, export_config_id: ExportConfigId, name: Annotated[Optional[str], Field(description='New name for the export configuration.')] = None, items: Annotated[Optional[List[str]], Field(description=ITEMS_DESC)] = None) Dict[str, Any][source]¶