rossum-mcp 0.3.4__py3-none-any.whl → 0.3.5__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.
- rossum_mcp/__init__.py +1 -1
- rossum_mcp/server.py +2 -0
- rossum_mcp/tools/__init__.py +2 -0
- rossum_mcp/tools/annotations.py +123 -86
- rossum_mcp/tools/base.py +22 -0
- rossum_mcp/tools/document_relations.py +1 -3
- rossum_mcp/tools/email_templates.py +131 -0
- rossum_mcp/tools/engines.py +106 -77
- rossum_mcp/tools/hooks.py +228 -150
- rossum_mcp/tools/queues.py +251 -85
- rossum_mcp/tools/relations.py +3 -7
- rossum_mcp/tools/rules.py +28 -17
- rossum_mcp/tools/schemas.py +501 -104
- rossum_mcp/tools/users.py +51 -27
- rossum_mcp/tools/workspaces.py +50 -37
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.3.5.dist-info}/METADATA +255 -5
- rossum_mcp-0.3.5.dist-info/RECORD +22 -0
- rossum_mcp-0.3.4.dist-info/RECORD +0 -21
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.3.5.dist-info}/WHEEL +0 -0
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.3.5.dist-info}/entry_points.txt +0 -0
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.3.5.dist-info}/top_level.txt +0 -0
rossum_mcp/__init__.py
CHANGED
rossum_mcp/server.py
CHANGED
|
@@ -18,6 +18,7 @@ from rossum_mcp.logging_config import setup_logging
|
|
|
18
18
|
from rossum_mcp.tools import (
|
|
19
19
|
register_annotation_tools,
|
|
20
20
|
register_document_relation_tools,
|
|
21
|
+
register_email_template_tools,
|
|
21
22
|
register_engine_tools,
|
|
22
23
|
register_hook_tools,
|
|
23
24
|
register_queue_tools,
|
|
@@ -49,6 +50,7 @@ register_queue_tools(mcp, client)
|
|
|
49
50
|
register_schema_tools(mcp, client)
|
|
50
51
|
register_engine_tools(mcp, client)
|
|
51
52
|
register_hook_tools(mcp, client)
|
|
53
|
+
register_email_template_tools(mcp, client)
|
|
52
54
|
register_document_relation_tools(mcp, client)
|
|
53
55
|
register_relation_tools(mcp, client)
|
|
54
56
|
register_rule_tools(mcp, client)
|
rossum_mcp/tools/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from rossum_mcp.tools.annotations import register_annotation_tools
|
|
6
6
|
from rossum_mcp.tools.document_relations import register_document_relation_tools
|
|
7
|
+
from rossum_mcp.tools.email_templates import register_email_template_tools
|
|
7
8
|
from rossum_mcp.tools.engines import register_engine_tools
|
|
8
9
|
from rossum_mcp.tools.hooks import register_hook_tools
|
|
9
10
|
from rossum_mcp.tools.queues import register_queue_tools
|
|
@@ -16,6 +17,7 @@ from rossum_mcp.tools.workspaces import register_workspace_tools
|
|
|
16
17
|
__all__ = [
|
|
17
18
|
"register_annotation_tools",
|
|
18
19
|
"register_document_relation_tools",
|
|
20
|
+
"register_email_template_tools",
|
|
19
21
|
"register_engine_tools",
|
|
20
22
|
"register_hook_tools",
|
|
21
23
|
"register_queue_tools",
|
rossum_mcp/tools/annotations.py
CHANGED
|
@@ -7,7 +7,7 @@ from collections.abc import Sequence # noqa: TC003 - needed at runtime for Fast
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TYPE_CHECKING, Literal
|
|
9
9
|
|
|
10
|
-
from rossum_api.models.annotation import Annotation
|
|
10
|
+
from rossum_api.models.annotation import Annotation
|
|
11
11
|
|
|
12
12
|
from rossum_mcp.tools.base import is_read_write_mode
|
|
13
13
|
|
|
@@ -17,114 +17,151 @@ if TYPE_CHECKING:
|
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
|
-
# Fixed sideloads (critical for well-behaving agent)
|
|
21
20
|
type Sideload = Literal["content", "document", "automation_blocker"]
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
def
|
|
23
|
+
async def _upload_document(client: AsyncRossumAPIClient, file_path: str, queue_id: int) -> dict:
|
|
24
|
+
if not is_read_write_mode():
|
|
25
|
+
return {"error": "upload_document is not available in read-only mode"}
|
|
26
|
+
|
|
27
|
+
path = Path(file_path)
|
|
28
|
+
if not path.exists():
|
|
29
|
+
logger.error(f"File not found: {file_path}")
|
|
30
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
task = (await client.upload_document(queue_id, [(str(path), path.name)]))[0]
|
|
34
|
+
except KeyError as e:
|
|
35
|
+
logger.error(f"Upload failed - unexpected API response format: {e!s}")
|
|
36
|
+
error_msg = (
|
|
37
|
+
f"Document upload failed - API response missing expected key {e!s}. "
|
|
38
|
+
f"This usually means either:\n"
|
|
39
|
+
f"1. The queue_id ({queue_id}) is invalid or you don't have access to it\n"
|
|
40
|
+
f"2. The Rossum API returned an error response\n"
|
|
41
|
+
f"Please verify:\n"
|
|
42
|
+
f"- The queue_id is correct and exists in your workspace\n"
|
|
43
|
+
f"- You have permission to upload documents to this queue\n"
|
|
44
|
+
f"- Your API token has the necessary permissions"
|
|
45
|
+
)
|
|
46
|
+
raise ValueError(error_msg) from e
|
|
47
|
+
except IndexError as e:
|
|
48
|
+
logger.error(f"Upload failed - no tasks returned: {e}")
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f"Document upload failed - no tasks were created. This may indicate the queue_id ({queue_id}) is invalid."
|
|
51
|
+
) from e
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.error(f"Upload failed: {type(e).__name__}: {e}")
|
|
54
|
+
raise ValueError(f"Document upload failed: {type(e).__name__}: {e!s}") from e
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"task_id": task.id,
|
|
58
|
+
"task_status": task.status,
|
|
59
|
+
"queue_id": queue_id,
|
|
60
|
+
"message": "Document upload initiated. Use `list_annotations` to find the annotation ID for this queue.",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def _get_annotation(
|
|
65
|
+
client: AsyncRossumAPIClient, annotation_id: int, sideloads: Sequence[Sideload] = ()
|
|
66
|
+
) -> Annotation:
|
|
67
|
+
logger.debug(f"Retrieving annotation: annotation_id={annotation_id}")
|
|
68
|
+
annotation: Annotation = await client.retrieve_annotation(annotation_id, sideloads) # type: ignore[arg-type]
|
|
69
|
+
return annotation
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def _list_annotations(
|
|
73
|
+
client: AsyncRossumAPIClient,
|
|
74
|
+
queue_id: int,
|
|
75
|
+
status: str | None = "importing,to_review,confirmed,exported",
|
|
76
|
+
ordering: Sequence[str] = (),
|
|
77
|
+
first_n: int | None = None,
|
|
78
|
+
) -> list[Annotation]:
|
|
79
|
+
logger.debug(f"Listing annotations: queue_id={queue_id}, status={status}, ordering={ordering}, first_n={first_n}")
|
|
80
|
+
params: dict = {"queue": queue_id, "page_size": 100}
|
|
81
|
+
if status:
|
|
82
|
+
params["status"] = status
|
|
83
|
+
if ordering:
|
|
84
|
+
params["ordering"] = ordering
|
|
85
|
+
|
|
86
|
+
annotations_list: list[Annotation] = []
|
|
87
|
+
async for item in client.list_annotations(**params):
|
|
88
|
+
annotations_list.append(item)
|
|
89
|
+
if first_n is not None and len(annotations_list) >= first_n:
|
|
90
|
+
break
|
|
91
|
+
return annotations_list
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def _start_annotation(client: AsyncRossumAPIClient, annotation_id: int) -> dict:
|
|
95
|
+
if not is_read_write_mode():
|
|
96
|
+
return {"error": "start_annotation is not available in read-only mode"}
|
|
97
|
+
|
|
98
|
+
logger.debug(f"Starting annotation: annotation_id={annotation_id}")
|
|
99
|
+
await client.start_annotation(annotation_id)
|
|
100
|
+
return {
|
|
101
|
+
"annotation_id": annotation_id,
|
|
102
|
+
"message": f"Annotation {annotation_id} started successfully. Status changed to 'reviewing'.",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def _bulk_update_annotation_fields(
|
|
107
|
+
client: AsyncRossumAPIClient, annotation_id: int, operations: list[dict]
|
|
108
|
+
) -> dict:
|
|
109
|
+
if not is_read_write_mode():
|
|
110
|
+
return {"error": "bulk_update_annotation_fields is not available in read-only mode"}
|
|
111
|
+
|
|
112
|
+
logger.debug(f"Bulk updating annotation: annotation_id={annotation_id}, ops={operations}")
|
|
113
|
+
await client.bulk_update_annotation_data(annotation_id, operations)
|
|
114
|
+
return {
|
|
115
|
+
"annotation_id": annotation_id,
|
|
116
|
+
"operations_count": len(operations),
|
|
117
|
+
"message": f"Annotation {annotation_id} updated with {len(operations)} operations successfully.",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def _confirm_annotation(client: AsyncRossumAPIClient, annotation_id: int) -> dict:
|
|
122
|
+
if not is_read_write_mode():
|
|
123
|
+
return {"error": "confirm_annotation is not available in read-only mode"}
|
|
124
|
+
|
|
125
|
+
logger.debug(f"Confirming annotation: annotation_id={annotation_id}")
|
|
126
|
+
await client.confirm_annotation(annotation_id)
|
|
127
|
+
return {
|
|
128
|
+
"annotation_id": annotation_id,
|
|
129
|
+
"message": f"Annotation {annotation_id} confirmed successfully. Status changed to 'confirmed'.",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def register_annotation_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
|
|
25
134
|
"""Register annotation-related tools with the FastMCP server."""
|
|
26
135
|
|
|
27
136
|
@mcp.tool(description="Upload a document to Rossum. Use list_annotations to get annotation ID.")
|
|
28
137
|
async def upload_document(file_path: str, queue_id: int) -> dict:
|
|
29
|
-
|
|
30
|
-
if not is_read_write_mode():
|
|
31
|
-
return {"error": "upload_document is not available in read-only mode"}
|
|
32
|
-
|
|
33
|
-
path = Path(file_path)
|
|
34
|
-
if not path.exists():
|
|
35
|
-
logger.error(f"File not found: {file_path}")
|
|
36
|
-
raise FileNotFoundError(f"File not found: {file_path}")
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
task = (await client.upload_document(queue_id, [(str(path), path.name)]))[0]
|
|
40
|
-
except KeyError as e:
|
|
41
|
-
logger.error(f"Upload failed - unexpected API response format: {e!s}")
|
|
42
|
-
error_msg = (
|
|
43
|
-
f"Document upload failed - API response missing expected key {e!s}. "
|
|
44
|
-
f"This usually means either:\n"
|
|
45
|
-
f"1. The queue_id ({queue_id}) is invalid or you don't have access to it\n"
|
|
46
|
-
f"2. The Rossum API returned an error response\n"
|
|
47
|
-
f"Please verify:\n"
|
|
48
|
-
f"- The queue_id is correct and exists in your workspace\n"
|
|
49
|
-
f"- You have permission to upload documents to this queue\n"
|
|
50
|
-
f"- Your API token has the necessary permissions"
|
|
51
|
-
)
|
|
52
|
-
raise ValueError(error_msg) from e
|
|
53
|
-
except IndexError as e:
|
|
54
|
-
logger.error(f"Upload failed - no tasks returned: {e}")
|
|
55
|
-
raise ValueError(
|
|
56
|
-
f"Document upload failed - no tasks were created. This may indicate the queue_id ({queue_id}) is invalid."
|
|
57
|
-
) from e
|
|
58
|
-
except Exception as e:
|
|
59
|
-
logger.error(f"Upload failed: {type(e).__name__}: {e}")
|
|
60
|
-
raise ValueError(f"Document upload failed: {type(e).__name__}: {e!s}") from e
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
"task_id": task.id,
|
|
64
|
-
"task_status": task.status,
|
|
65
|
-
"queue_id": queue_id,
|
|
66
|
-
"message": "Document upload initiated. Use `list_annotations` to find the annotation ID for this queue.",
|
|
67
|
-
}
|
|
138
|
+
return await _upload_document(client, file_path, queue_id)
|
|
68
139
|
|
|
69
140
|
@mcp.tool(description="Retrieve annotation data. Use 'content' sideload to get extracted data.")
|
|
70
141
|
async def get_annotation(annotation_id: int, sideloads: Sequence[Sideload] = ()) -> Annotation:
|
|
71
|
-
|
|
72
|
-
logger.debug(f"Retrieving annotation: annotation_id={annotation_id}")
|
|
73
|
-
annotation: Annotation = await client.retrieve_annotation(annotation_id, sideloads) # type: ignore[arg-type]
|
|
74
|
-
return annotation
|
|
142
|
+
return await _get_annotation(client, annotation_id, sideloads)
|
|
75
143
|
|
|
76
|
-
@mcp.tool(description="List annotations for a queue.")
|
|
144
|
+
@mcp.tool(description="List annotations for a queue. Use ordering=['-created_at'] to sort by newest first.")
|
|
77
145
|
async def list_annotations(
|
|
78
|
-
queue_id: int,
|
|
146
|
+
queue_id: int,
|
|
147
|
+
status: str | None = "importing,to_review,confirmed,exported",
|
|
148
|
+
ordering: Sequence[str] = (),
|
|
149
|
+
first_n: int | None = None,
|
|
79
150
|
) -> list[Annotation]:
|
|
80
|
-
|
|
81
|
-
logger.debug(f"Listing annotations: queue_id={queue_id}, status={status}")
|
|
82
|
-
params: dict = {"queue": queue_id, "page_size": 100}
|
|
83
|
-
if status:
|
|
84
|
-
params["status"] = status
|
|
85
|
-
annotations_list: list[Annotation] = [item async for item in client.list_annotations(**params)]
|
|
86
|
-
return annotations_list
|
|
151
|
+
return await _list_annotations(client, queue_id, status, ordering, first_n)
|
|
87
152
|
|
|
88
153
|
@mcp.tool(description="Start annotation (move from 'to_review' to 'reviewing').")
|
|
89
154
|
async def start_annotation(annotation_id: int) -> dict:
|
|
90
|
-
|
|
91
|
-
if not is_read_write_mode():
|
|
92
|
-
return {"error": "start_annotation is not available in read-only mode"}
|
|
93
|
-
|
|
94
|
-
logger.debug(f"Starting annotation: annotation_id={annotation_id}")
|
|
95
|
-
await client.start_annotation(annotation_id)
|
|
96
|
-
return {
|
|
97
|
-
"annotation_id": annotation_id,
|
|
98
|
-
"message": f"Annotation {annotation_id} started successfully. Status changed to 'reviewing'.",
|
|
99
|
-
}
|
|
155
|
+
return await _start_annotation(client, annotation_id)
|
|
100
156
|
|
|
101
157
|
@mcp.tool(
|
|
102
158
|
description="Bulk update annotation fields. It can be used after `start_annotation` only. Use datapoint ID from content, NOT schema_id."
|
|
103
159
|
)
|
|
104
160
|
async def bulk_update_annotation_fields(annotation_id: int, operations: list[dict]) -> dict:
|
|
105
|
-
|
|
106
|
-
if not is_read_write_mode():
|
|
107
|
-
return {"error": "bulk_update_annotation_fields is not available in read-only mode"}
|
|
108
|
-
|
|
109
|
-
logger.debug(f"Bulk updating annotation: annotation_id={annotation_id}, ops={operations}")
|
|
110
|
-
await client.bulk_update_annotation_data(annotation_id, operations)
|
|
111
|
-
return {
|
|
112
|
-
"annotation_id": annotation_id,
|
|
113
|
-
"operations_count": len(operations),
|
|
114
|
-
"message": f"Annotation {annotation_id} updated with {len(operations)} operations successfully.",
|
|
115
|
-
}
|
|
161
|
+
return await _bulk_update_annotation_fields(client, annotation_id, operations)
|
|
116
162
|
|
|
117
163
|
@mcp.tool(
|
|
118
164
|
description="Confirm annotation (move to 'confirmed'). It can be used after `bulk_update_annotation_fields`."
|
|
119
165
|
)
|
|
120
166
|
async def confirm_annotation(annotation_id: int) -> dict:
|
|
121
|
-
|
|
122
|
-
if not is_read_write_mode():
|
|
123
|
-
return {"error": "confirm_annotation is not available in read-only mode"}
|
|
124
|
-
|
|
125
|
-
logger.debug(f"Confirming annotation: annotation_id={annotation_id}")
|
|
126
|
-
await client.confirm_annotation(annotation_id)
|
|
127
|
-
return {
|
|
128
|
-
"annotation_id": annotation_id,
|
|
129
|
-
"message": f"Annotation {annotation_id} confirmed successfully. Status changed to 'confirmed'.",
|
|
130
|
-
}
|
|
167
|
+
return await _confirm_annotation(client, annotation_id)
|
rossum_mcp/tools/base.py
CHANGED
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from typing import Any
|
|
6
10
|
|
|
7
11
|
BASE_URL = os.environ.get("ROSSUM_API_BASE_URL", "").rstrip("/")
|
|
8
12
|
MODE = os.environ.get("ROSSUM_MCP_MODE", "read-write").lower()
|
|
9
13
|
|
|
14
|
+
# Marker used to indicate omitted fields in list responses
|
|
15
|
+
TRUNCATED_MARKER = "<omitted>"
|
|
16
|
+
|
|
10
17
|
|
|
11
18
|
def build_resource_url(resource_type: str, resource_id: int) -> str:
|
|
12
19
|
"""Build a full URL for a Rossum API resource."""
|
|
@@ -16,3 +23,18 @@ def build_resource_url(resource_type: str, resource_id: int) -> str:
|
|
|
16
23
|
def is_read_write_mode() -> bool:
|
|
17
24
|
"""Check if server is in read-write mode."""
|
|
18
25
|
return MODE == "read-write"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def truncate_dict_fields(data: dict[str, Any], fields: tuple[str, ...]) -> dict[str, Any]:
|
|
29
|
+
"""Truncate specified fields in a dictionary to save context.
|
|
30
|
+
|
|
31
|
+
Returns a new dictionary with specified fields replaced by TRUNCATED_MARKER.
|
|
32
|
+
"""
|
|
33
|
+
if not data:
|
|
34
|
+
return data
|
|
35
|
+
|
|
36
|
+
result = dict(data)
|
|
37
|
+
for field in fields:
|
|
38
|
+
if field in result:
|
|
39
|
+
result[field] = TRUNCATED_MARKER
|
|
40
|
+
return result
|
|
@@ -5,9 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from rossum_api.models.document_relation import
|
|
9
|
-
DocumentRelation, # noqa: TC002 - needed at runtime for FastMCP
|
|
10
|
-
)
|
|
8
|
+
from rossum_api.models.document_relation import DocumentRelation
|
|
11
9
|
|
|
12
10
|
if TYPE_CHECKING:
|
|
13
11
|
from fastmcp import FastMCP
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Email template tools for Rossum MCP Server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
7
|
+
|
|
8
|
+
from rossum_api.models.email_template import EmailTemplate
|
|
9
|
+
|
|
10
|
+
from rossum_mcp.tools.base import is_read_write_mode
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from fastmcp import FastMCP
|
|
14
|
+
from rossum_api import AsyncRossumAPIClient
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
EmailTemplateType = Literal["rejection", "rejection_default", "email_with_no_processable_attachments", "custom"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def _get_email_template(client: AsyncRossumAPIClient, email_template_id: int) -> EmailTemplate:
|
|
22
|
+
email_template: EmailTemplate = await client.retrieve_email_template(email_template_id)
|
|
23
|
+
return email_template
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def _list_email_templates(
|
|
27
|
+
client: AsyncRossumAPIClient,
|
|
28
|
+
queue_id: int | None = None,
|
|
29
|
+
type: EmailTemplateType | None = None,
|
|
30
|
+
name: str | None = None,
|
|
31
|
+
first_n: int | None = None,
|
|
32
|
+
) -> list[EmailTemplate]:
|
|
33
|
+
filters: dict = {}
|
|
34
|
+
if queue_id is not None:
|
|
35
|
+
filters["queue"] = queue_id
|
|
36
|
+
if type is not None:
|
|
37
|
+
filters["type"] = type
|
|
38
|
+
if name is not None:
|
|
39
|
+
filters["name"] = name
|
|
40
|
+
|
|
41
|
+
if first_n is not None:
|
|
42
|
+
templates_iter = client.list_email_templates(**filters)
|
|
43
|
+
templates_list: list[EmailTemplate] = []
|
|
44
|
+
n = 0
|
|
45
|
+
while n < first_n:
|
|
46
|
+
templates_list.append(await anext(templates_iter))
|
|
47
|
+
n += 1
|
|
48
|
+
else:
|
|
49
|
+
templates_list = [template async for template in client.list_email_templates(**filters)]
|
|
50
|
+
|
|
51
|
+
return templates_list
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _create_email_template(
|
|
55
|
+
client: AsyncRossumAPIClient,
|
|
56
|
+
name: str,
|
|
57
|
+
queue: str,
|
|
58
|
+
subject: str,
|
|
59
|
+
message: str,
|
|
60
|
+
type: EmailTemplateType = "custom",
|
|
61
|
+
automate: bool = False,
|
|
62
|
+
to: list[dict[str, Any]] | None = None,
|
|
63
|
+
cc: list[dict[str, Any]] | None = None,
|
|
64
|
+
bcc: list[dict[str, Any]] | None = None,
|
|
65
|
+
triggers: list[str] | None = None,
|
|
66
|
+
) -> EmailTemplate | dict:
|
|
67
|
+
if not is_read_write_mode():
|
|
68
|
+
return {"error": "create_email_template is not available in read-only mode"}
|
|
69
|
+
|
|
70
|
+
logger.debug(f"Creating email template: name={name}, queue={queue}, type={type}")
|
|
71
|
+
|
|
72
|
+
template_data: dict[str, Any] = {
|
|
73
|
+
"name": name,
|
|
74
|
+
"queue": queue,
|
|
75
|
+
"subject": subject,
|
|
76
|
+
"message": message,
|
|
77
|
+
"type": type,
|
|
78
|
+
"automate": automate,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if to is not None:
|
|
82
|
+
template_data["to"] = to
|
|
83
|
+
if cc is not None:
|
|
84
|
+
template_data["cc"] = cc
|
|
85
|
+
if bcc is not None:
|
|
86
|
+
template_data["bcc"] = bcc
|
|
87
|
+
if triggers is not None:
|
|
88
|
+
template_data["triggers"] = triggers
|
|
89
|
+
|
|
90
|
+
email_template: EmailTemplate = await client.create_new_email_template(template_data)
|
|
91
|
+
return email_template
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def register_email_template_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
|
|
95
|
+
"""Register email template-related tools with the FastMCP server."""
|
|
96
|
+
|
|
97
|
+
@mcp.tool(
|
|
98
|
+
description="Retrieve a single email template by ID. Use list_email_templates first to find templates for a queue."
|
|
99
|
+
)
|
|
100
|
+
async def get_email_template(email_template_id: int) -> EmailTemplate:
|
|
101
|
+
return await _get_email_template(client, email_template_id)
|
|
102
|
+
|
|
103
|
+
@mcp.tool(
|
|
104
|
+
description="List all email templates with optional filtering. Email templates define automated or manual email responses sent from Rossum queues. Types: 'rejection' (for rejecting documents), 'rejection_default' (default rejection template), 'email_with_no_processable_attachments' (when email has no valid attachments), 'custom' (user-defined templates)."
|
|
105
|
+
)
|
|
106
|
+
async def list_email_templates(
|
|
107
|
+
queue_id: int | None = None,
|
|
108
|
+
type: EmailTemplateType | None = None,
|
|
109
|
+
name: str | None = None,
|
|
110
|
+
first_n: int | None = None,
|
|
111
|
+
) -> list[EmailTemplate]:
|
|
112
|
+
return await _list_email_templates(client, queue_id, type, name, first_n)
|
|
113
|
+
|
|
114
|
+
@mcp.tool(
|
|
115
|
+
description="Create a new email template. Email templates can be automated (automate=True) to send emails automatically on specific triggers, or manual for user-initiated sending. The 'to', 'cc', and 'bcc' fields accept lists of recipient objects with 'type' ('annotator', 'constant', 'datapoint') and 'value' keys."
|
|
116
|
+
)
|
|
117
|
+
async def create_email_template(
|
|
118
|
+
name: str,
|
|
119
|
+
queue: str,
|
|
120
|
+
subject: str,
|
|
121
|
+
message: str,
|
|
122
|
+
type: EmailTemplateType = "custom",
|
|
123
|
+
automate: bool = False,
|
|
124
|
+
to: list[dict[str, Any]] | None = None,
|
|
125
|
+
cc: list[dict[str, Any]] | None = None,
|
|
126
|
+
bcc: list[dict[str, Any]] | None = None,
|
|
127
|
+
triggers: list[str] | None = None,
|
|
128
|
+
) -> EmailTemplate | dict:
|
|
129
|
+
return await _create_email_template(
|
|
130
|
+
client, name, queue, subject, message, type, automate, to, cc, bcc, triggers
|
|
131
|
+
)
|