rossum-mcp 0.3.4__py3-none-any.whl → 0.4.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.
- rossum_mcp/__init__.py +1 -1
- rossum_mcp/server.py +4 -0
- rossum_mcp/tools/__init__.py +14 -0
- rossum_mcp/tools/annotations.py +123 -86
- rossum_mcp/tools/base.py +22 -0
- rossum_mcp/tools/catalog.py +169 -0
- rossum_mcp/tools/discovery.py +36 -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 +244 -85
- rossum_mcp/tools/relations.py +3 -7
- rossum_mcp/tools/rules.py +28 -17
- rossum_mcp/tools/schemas.py +505 -105
- rossum_mcp/tools/users.py +51 -27
- rossum_mcp/tools/workspaces.py +47 -37
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.4.0.dist-info}/METADATA +370 -12
- rossum_mcp-0.4.0.dist-info/RECORD +24 -0
- rossum_mcp-0.3.4.dist-info/RECORD +0 -21
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.4.0.dist-info}/WHEEL +0 -0
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.4.0.dist-info}/entry_points.txt +0 -0
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
)
|
rossum_mcp/tools/engines.py
CHANGED
|
@@ -3,14 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
from typing import TYPE_CHECKING, Literal, cast
|
|
7
7
|
|
|
8
8
|
from rossum_api.domain_logic.resources import Resource
|
|
9
|
-
from rossum_api.models.engine import
|
|
10
|
-
Engine, # noqa: TC002 - needed at runtime for FastMCP
|
|
11
|
-
EngineField, # noqa: TC002 - needed at runtime for FastMCP
|
|
12
|
-
EngineFieldType, # noqa: TC002 - needed at runtime for FastMCP
|
|
13
|
-
)
|
|
9
|
+
from rossum_api.models.engine import Engine, EngineField, EngineFieldType
|
|
14
10
|
|
|
15
11
|
from rossum_mcp.tools.base import build_resource_url, is_read_write_mode
|
|
16
12
|
|
|
@@ -23,62 +19,124 @@ if TYPE_CHECKING:
|
|
|
23
19
|
logger = logging.getLogger(__name__)
|
|
24
20
|
|
|
25
21
|
|
|
26
|
-
def
|
|
22
|
+
async def _get_engine(client: AsyncRossumAPIClient, engine_id: int) -> Engine:
|
|
23
|
+
logger.debug(f"Retrieving engine: engine_id={engine_id}")
|
|
24
|
+
engine: Engine = await client.retrieve_engine(engine_id)
|
|
25
|
+
return engine
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def _list_engines(
|
|
29
|
+
client: AsyncRossumAPIClient,
|
|
30
|
+
id: int | None = None,
|
|
31
|
+
engine_type: EngineType | None = None,
|
|
32
|
+
agenda_id: str | None = None,
|
|
33
|
+
) -> list[Engine]:
|
|
34
|
+
logger.debug(f"Listing engines: id={id}, type={engine_type}, agenda_id={agenda_id}")
|
|
35
|
+
filters: dict[str, int | str] = {}
|
|
36
|
+
if id is not None:
|
|
37
|
+
filters["id"] = id
|
|
38
|
+
if engine_type is not None:
|
|
39
|
+
filters["type"] = engine_type
|
|
40
|
+
if agenda_id is not None:
|
|
41
|
+
filters["agenda_id"] = agenda_id
|
|
42
|
+
return [engine async for engine in client.list_engines(**filters)] # type: ignore[arg-type]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def _update_engine(client: AsyncRossumAPIClient, engine_id: int, engine_data: dict) -> Engine | dict:
|
|
46
|
+
if not is_read_write_mode():
|
|
47
|
+
return {"error": "update_engine is not available in read-only mode"}
|
|
48
|
+
|
|
49
|
+
logger.debug(f"Updating engine: engine_id={engine_id}, data={engine_data}")
|
|
50
|
+
updated_engine_data = await client._http_client.update(Resource.Engine, engine_id, engine_data)
|
|
51
|
+
return cast("Engine", client._deserializer(Resource.Engine, updated_engine_data))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _create_engine(
|
|
55
|
+
client: AsyncRossumAPIClient, name: str, organization_id: int, engine_type: EngineType
|
|
56
|
+
) -> Engine | dict:
|
|
57
|
+
if not is_read_write_mode():
|
|
58
|
+
return {"error": "create_engine is not available in read-only mode"}
|
|
59
|
+
|
|
60
|
+
if engine_type not in ("extractor", "splitter"):
|
|
61
|
+
raise ValueError(f"Invalid engine_type '{engine_type}'. Must be 'extractor' or 'splitter'")
|
|
62
|
+
|
|
63
|
+
logger.debug(f"Creating engine: name={name}, organization_id={organization_id}, type={engine_type}")
|
|
64
|
+
engine_data = {
|
|
65
|
+
"name": name,
|
|
66
|
+
"organization": build_resource_url("organizations", organization_id),
|
|
67
|
+
"type": engine_type,
|
|
68
|
+
}
|
|
69
|
+
engine_response = await client._http_client.create(Resource.Engine, engine_data)
|
|
70
|
+
return cast("Engine", client._deserializer(Resource.Engine, engine_response))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def _create_engine_field(
|
|
74
|
+
client: AsyncRossumAPIClient,
|
|
75
|
+
engine_id: int,
|
|
76
|
+
name: str,
|
|
77
|
+
label: str,
|
|
78
|
+
field_type: EngineFieldType,
|
|
79
|
+
schema_ids: list[int],
|
|
80
|
+
tabular: bool = False,
|
|
81
|
+
multiline: str = "false",
|
|
82
|
+
subtype: str | None = None,
|
|
83
|
+
pre_trained_field_id: str | None = None,
|
|
84
|
+
) -> EngineField | dict:
|
|
85
|
+
if not is_read_write_mode():
|
|
86
|
+
return {"error": "create_engine_field is not available in read-only mode"}
|
|
87
|
+
|
|
88
|
+
valid_types = ("string", "number", "date", "enum")
|
|
89
|
+
if field_type not in valid_types:
|
|
90
|
+
raise ValueError(f"Invalid field_type '{field_type}'. Must be one of: {', '.join(valid_types)}")
|
|
91
|
+
if not schema_ids:
|
|
92
|
+
raise ValueError("schema_ids cannot be empty - engine field must be linked to at least one schema")
|
|
93
|
+
|
|
94
|
+
logger.debug(f"Creating engine field: engine_id={engine_id}, name={name}, type={field_type}, schemas={schema_ids}")
|
|
95
|
+
engine_field_data = {
|
|
96
|
+
"engine": build_resource_url("engines", engine_id),
|
|
97
|
+
"name": name,
|
|
98
|
+
"label": label,
|
|
99
|
+
"type": field_type,
|
|
100
|
+
"tabular": tabular,
|
|
101
|
+
"multiline": multiline,
|
|
102
|
+
"schemas": [build_resource_url("schemas", schema_id) for schema_id in schema_ids],
|
|
103
|
+
}
|
|
104
|
+
if subtype is not None:
|
|
105
|
+
engine_field_data["subtype"] = subtype
|
|
106
|
+
if pre_trained_field_id is not None:
|
|
107
|
+
engine_field_data["pre_trained_field_id"] = pre_trained_field_id
|
|
108
|
+
|
|
109
|
+
engine_field_response = await client._http_client.create(Resource.EngineField, engine_field_data)
|
|
110
|
+
return cast("EngineField", client._deserializer(Resource.EngineField, engine_field_response))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def _get_engine_fields(client: AsyncRossumAPIClient, engine_id: int | None = None) -> list[EngineField]:
|
|
114
|
+
logger.debug(f"Retrieving engine fields: engine_id={engine_id}")
|
|
115
|
+
return [engine_field async for engine_field in client.retrieve_engine_fields(engine_id=engine_id)]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def register_engine_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
|
|
27
119
|
"""Register engine-related tools with the FastMCP server."""
|
|
28
120
|
|
|
29
121
|
@mcp.tool(description="Retrieve a single engine by ID.")
|
|
30
122
|
async def get_engine(engine_id: int) -> Engine:
|
|
31
|
-
|
|
32
|
-
logger.debug(f"Retrieving engine: engine_id={engine_id}")
|
|
33
|
-
engine: Engine = await client.retrieve_engine(engine_id)
|
|
34
|
-
return engine
|
|
123
|
+
return await _get_engine(client, engine_id)
|
|
35
124
|
|
|
36
125
|
@mcp.tool(description="List all engines with optional filters.")
|
|
37
126
|
async def list_engines(
|
|
38
127
|
id: int | None = None, engine_type: EngineType | None = None, agenda_id: str | None = None
|
|
39
128
|
) -> list[Engine]:
|
|
40
|
-
|
|
41
|
-
logger.debug(f"Listing engines: id={id}, type={engine_type}, agenda_id={agenda_id}")
|
|
42
|
-
filters: dict[str, int | str] = {}
|
|
43
|
-
if id is not None:
|
|
44
|
-
filters["id"] = id
|
|
45
|
-
if engine_type is not None:
|
|
46
|
-
filters["type"] = engine_type
|
|
47
|
-
if agenda_id is not None:
|
|
48
|
-
filters["agenda_id"] = agenda_id
|
|
49
|
-
return [engine async for engine in client.list_engines(**filters)] # type: ignore[arg-type]
|
|
129
|
+
return await _list_engines(client, id, engine_type, agenda_id)
|
|
50
130
|
|
|
51
131
|
@mcp.tool(description="Update engine settings.")
|
|
52
132
|
async def update_engine(engine_id: int, engine_data: dict) -> Engine | dict:
|
|
53
|
-
|
|
54
|
-
if not is_read_write_mode():
|
|
55
|
-
return {"error": "update_engine is not available in read-only mode"}
|
|
56
|
-
|
|
57
|
-
logger.debug(f"Updating engine: engine_id={engine_id}, data={engine_data}")
|
|
58
|
-
updated_engine_data = await client._http_client.update(Resource.Engine, engine_id, engine_data)
|
|
59
|
-
updated_engine: Engine = client._deserializer(Resource.Engine, updated_engine_data)
|
|
60
|
-
return updated_engine
|
|
133
|
+
return await _update_engine(client, engine_id, engine_data)
|
|
61
134
|
|
|
62
135
|
@mcp.tool(
|
|
63
136
|
description="Create a new engine. IMPORTANT: When creating a new engine, check the schema to be used and create contained Engine fields immediately!"
|
|
64
137
|
)
|
|
65
138
|
async def create_engine(name: str, organization_id: int, engine_type: EngineType) -> Engine | dict:
|
|
66
|
-
|
|
67
|
-
if not is_read_write_mode():
|
|
68
|
-
return {"error": "create_engine is not available in read-only mode"}
|
|
69
|
-
|
|
70
|
-
if engine_type not in ("extractor", "splitter"):
|
|
71
|
-
raise ValueError(f"Invalid engine_type '{engine_type}'. Must be 'extractor' or 'splitter'")
|
|
72
|
-
|
|
73
|
-
logger.debug(f"Creating engine: name={name}, organization_id={organization_id}, type={engine_type}")
|
|
74
|
-
engine_data = {
|
|
75
|
-
"name": name,
|
|
76
|
-
"organization": build_resource_url("organizations", organization_id),
|
|
77
|
-
"type": engine_type,
|
|
78
|
-
}
|
|
79
|
-
engine_response = await client._http_client.create(Resource.Engine, engine_data)
|
|
80
|
-
engine: Engine = client._deserializer(Resource.Engine, engine_response)
|
|
81
|
-
return engine
|
|
139
|
+
return await _create_engine(client, name, organization_id, engine_type)
|
|
82
140
|
|
|
83
141
|
@mcp.tool(description="Create engine field for each schema field. Must be called when creating engine + schema.")
|
|
84
142
|
async def create_engine_field(
|
|
@@ -92,39 +150,10 @@ def register_engine_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
|
|
|
92
150
|
subtype: str | None = None,
|
|
93
151
|
pre_trained_field_id: str | None = None,
|
|
94
152
|
) -> EngineField | dict:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return {"error": "create_engine_field is not available in read-only mode"}
|
|
98
|
-
|
|
99
|
-
valid_types = ("string", "number", "date", "enum")
|
|
100
|
-
if field_type not in valid_types:
|
|
101
|
-
raise ValueError(f"Invalid field_type '{field_type}'. Must be one of: {', '.join(valid_types)}")
|
|
102
|
-
if not schema_ids:
|
|
103
|
-
raise ValueError("schema_ids cannot be empty - engine field must be linked to at least one schema")
|
|
104
|
-
|
|
105
|
-
logger.debug(
|
|
106
|
-
f"Creating engine field: engine_id={engine_id}, name={name}, type={field_type}, schemas={schema_ids}"
|
|
153
|
+
return await _create_engine_field(
|
|
154
|
+
client, engine_id, name, label, field_type, schema_ids, tabular, multiline, subtype, pre_trained_field_id
|
|
107
155
|
)
|
|
108
|
-
engine_field_data = {
|
|
109
|
-
"engine": build_resource_url("engines", engine_id),
|
|
110
|
-
"name": name,
|
|
111
|
-
"label": label,
|
|
112
|
-
"type": field_type,
|
|
113
|
-
"tabular": tabular,
|
|
114
|
-
"multiline": multiline,
|
|
115
|
-
"schemas": [build_resource_url("schemas", schema_id) for schema_id in schema_ids],
|
|
116
|
-
}
|
|
117
|
-
if subtype is not None:
|
|
118
|
-
engine_field_data["subtype"] = subtype
|
|
119
|
-
if pre_trained_field_id is not None:
|
|
120
|
-
engine_field_data["pre_trained_field_id"] = pre_trained_field_id
|
|
121
|
-
|
|
122
|
-
engine_field_response = await client._http_client.create(Resource.EngineField, engine_field_data)
|
|
123
|
-
engine_field: EngineField = client._deserializer(Resource.EngineField, engine_field_response)
|
|
124
|
-
return engine_field
|
|
125
156
|
|
|
126
157
|
@mcp.tool(description="Retrieve engine fields for a specific engine or all engine fields.")
|
|
127
158
|
async def get_engine_fields(engine_id: int | None = None) -> list[EngineField]:
|
|
128
|
-
|
|
129
|
-
logger.debug(f"Retrieving engine fields: engine_id={engine_id}")
|
|
130
|
-
return [engine_field async for engine_field in client.retrieve_engine_fields(engine_id=engine_id)]
|
|
159
|
+
return await _get_engine_fields(client, engine_id)
|