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.
@@ -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
+ )
@@ -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 register_engine_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None: # noqa: C901
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
- """Retrieve a single engine by ID."""
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
- """List all engines with optional filters."""
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
- """Update an existing engine's settings."""
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
- """Create a new engine."""
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
- """Create a new engine field and link it to schemas."""
96
- if not is_read_write_mode():
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
- """Retrieve engine fields for a specific engine or all engine fields."""
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)