rossum-mcp 0.3.4__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 +3 -0
- rossum_mcp/logging_config.py +141 -0
- rossum_mcp/py.typed +0 -0
- rossum_mcp/server.py +65 -0
- rossum_mcp/tools/__init__.py +27 -0
- rossum_mcp/tools/annotations.py +130 -0
- rossum_mcp/tools/base.py +18 -0
- rossum_mcp/tools/document_relations.py +58 -0
- rossum_mcp/tools/engines.py +130 -0
- rossum_mcp/tools/hooks.py +265 -0
- rossum_mcp/tools/queues.py +133 -0
- rossum_mcp/tools/relations.py +56 -0
- rossum_mcp/tools/rules.py +42 -0
- rossum_mcp/tools/schemas.py +384 -0
- rossum_mcp/tools/users.py +60 -0
- rossum_mcp/tools/workspaces.py +65 -0
- rossum_mcp-0.3.4.dist-info/METADATA +1537 -0
- rossum_mcp-0.3.4.dist-info/RECORD +21 -0
- rossum_mcp-0.3.4.dist-info/WHEEL +5 -0
- rossum_mcp-0.3.4.dist-info/entry_points.txt +2 -0
- rossum_mcp-0.3.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Hook tools for Rossum MCP Server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
8
|
+
|
|
9
|
+
from rossum_api.models.hook import ( # noqa: TC002 - needed at runtime for FastMCP
|
|
10
|
+
Hook,
|
|
11
|
+
HookRunData,
|
|
12
|
+
HookType,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from rossum_mcp.tools.base import is_read_write_mode
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from fastmcp import FastMCP
|
|
19
|
+
from rossum_api import AsyncRossumAPIClient
|
|
20
|
+
|
|
21
|
+
type Timestamp = Annotated[str, "ISO 8601 timestamp (e.g., '2024-01-15T10:30:00Z')"]
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class HookTemplate:
|
|
28
|
+
"""Represents a hook template from Rossum Store.
|
|
29
|
+
|
|
30
|
+
Hook templates provide pre-built extension configurations that can be
|
|
31
|
+
used to quickly create hooks with standard functionality.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
id: int
|
|
35
|
+
url: str
|
|
36
|
+
name: str
|
|
37
|
+
description: str
|
|
38
|
+
type: str
|
|
39
|
+
events: list[str]
|
|
40
|
+
config: dict[str, Any]
|
|
41
|
+
settings_schema: dict[str, Any] | None
|
|
42
|
+
guide: str | None
|
|
43
|
+
use_token_owner: bool
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def register_hook_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None: # noqa: C901
|
|
47
|
+
"""Register hook-related tools with the FastMCP server."""
|
|
48
|
+
|
|
49
|
+
@mcp.tool(
|
|
50
|
+
description="Retrieve a single hook by ID. Use list_hooks first to get all hooks for a queue - only use get_hook if you need additional details for a specific hook not returned by list_hooks. For Python-based function hooks, the source code is accessible via hook.config['code']."
|
|
51
|
+
)
|
|
52
|
+
async def get_hook(hook_id: int) -> Hook:
|
|
53
|
+
hook: Hook = await client.retrieve_hook(hook_id)
|
|
54
|
+
return hook
|
|
55
|
+
|
|
56
|
+
@mcp.tool(
|
|
57
|
+
description="List all hooks/extensions for a queue. ALWAYS use this first when you need information about hooks on a queue - it returns complete hook details including code, config, and settings in a single call. Only use get_hook afterward if you need details not present in the list response. For Python-based function hooks, the source code is accessible via hook.config['code']."
|
|
58
|
+
)
|
|
59
|
+
async def list_hooks(
|
|
60
|
+
queue_id: int | None = None, active: bool | None = None, first_n: int | None = None
|
|
61
|
+
) -> list[Hook]:
|
|
62
|
+
filters: dict = {}
|
|
63
|
+
if queue_id is not None:
|
|
64
|
+
filters["queue"] = queue_id
|
|
65
|
+
if active is not None:
|
|
66
|
+
filters["active"] = active
|
|
67
|
+
|
|
68
|
+
if first_n is not None:
|
|
69
|
+
hooks_iter = client.list_hooks(**filters)
|
|
70
|
+
hooks_list: list[Hook] = []
|
|
71
|
+
n = 0
|
|
72
|
+
while n < first_n:
|
|
73
|
+
hooks_list.append(await anext(hooks_iter))
|
|
74
|
+
n += 1
|
|
75
|
+
else:
|
|
76
|
+
hooks_list = [hook async for hook in client.list_hooks(**filters)]
|
|
77
|
+
|
|
78
|
+
return hooks_list
|
|
79
|
+
|
|
80
|
+
# NOTE: We explicitly document token_owner restrictions in the tool description because
|
|
81
|
+
# Sonnet 4.5 respects tool descriptions more reliably than instructions in system prompts.
|
|
82
|
+
@mcp.tool(
|
|
83
|
+
description="Create a new hook. If token_owner is provided, organization_group_admin users CANNOT be used (API will reject)."
|
|
84
|
+
)
|
|
85
|
+
async def create_hook(
|
|
86
|
+
name: str,
|
|
87
|
+
type: HookType,
|
|
88
|
+
queues: list[str] | None = None,
|
|
89
|
+
events: list[str] | None = None,
|
|
90
|
+
config: dict | None = None,
|
|
91
|
+
settings: dict | None = None,
|
|
92
|
+
secret: str | None = None,
|
|
93
|
+
) -> Hook | dict:
|
|
94
|
+
if not is_read_write_mode():
|
|
95
|
+
return {"error": "create_hook is not available in read-only mode"}
|
|
96
|
+
|
|
97
|
+
hook_data: dict[str, Any] = {"name": name, "type": type, "sideload": ["schemas"]}
|
|
98
|
+
|
|
99
|
+
if queues is not None:
|
|
100
|
+
hook_data["queues"] = queues
|
|
101
|
+
if events is not None:
|
|
102
|
+
hook_data["events"] = events
|
|
103
|
+
if config is None:
|
|
104
|
+
config = {}
|
|
105
|
+
if type == "function" and "source" in config:
|
|
106
|
+
config["function"] = config.pop("source")
|
|
107
|
+
if type == "function" and "runtime" not in config:
|
|
108
|
+
config["runtime"] = "python3.12"
|
|
109
|
+
if "timeout_s" in config and config["timeout_s"] > 60:
|
|
110
|
+
config["timeout_s"] = 60
|
|
111
|
+
hook_data["config"] = config
|
|
112
|
+
if settings is not None:
|
|
113
|
+
hook_data["settings"] = settings
|
|
114
|
+
if secret is not None:
|
|
115
|
+
hook_data["secret"] = secret
|
|
116
|
+
|
|
117
|
+
hook: Hook = await client.create_new_hook(hook_data)
|
|
118
|
+
return hook
|
|
119
|
+
|
|
120
|
+
@mcp.tool(
|
|
121
|
+
description="Update an existing hook. Use this to modify hook properties like name, queues, config, events, or settings. Only provide the fields you want to change - other fields will remain unchanged."
|
|
122
|
+
)
|
|
123
|
+
async def update_hook(
|
|
124
|
+
hook_id: int,
|
|
125
|
+
name: str | None = None,
|
|
126
|
+
queues: list[str] | None = None,
|
|
127
|
+
events: list[str] | None = None,
|
|
128
|
+
config: dict | None = None,
|
|
129
|
+
settings: dict | None = None,
|
|
130
|
+
active: bool | None = None,
|
|
131
|
+
) -> Hook | dict:
|
|
132
|
+
if not is_read_write_mode():
|
|
133
|
+
return {"error": "update_hook is not available in read-only mode"}
|
|
134
|
+
|
|
135
|
+
logger.debug(f"Updating hook: hook_id={hook_id}")
|
|
136
|
+
|
|
137
|
+
# Fetch existing hook data first (PUT requires all fields)
|
|
138
|
+
existing_hook: Hook = await client.retrieve_hook(hook_id)
|
|
139
|
+
hook_data: dict[str, Any] = {
|
|
140
|
+
"name": existing_hook.name,
|
|
141
|
+
"queues": existing_hook.queues,
|
|
142
|
+
"events": list(existing_hook.events),
|
|
143
|
+
"config": dict(existing_hook.config) if existing_hook.config else {},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Override with provided values
|
|
147
|
+
if name is not None:
|
|
148
|
+
hook_data["name"] = name
|
|
149
|
+
if queues is not None:
|
|
150
|
+
hook_data["queues"] = queues
|
|
151
|
+
if events is not None:
|
|
152
|
+
hook_data["events"] = events
|
|
153
|
+
if config is not None:
|
|
154
|
+
hook_data["config"] = config
|
|
155
|
+
if settings is not None:
|
|
156
|
+
hook_data["settings"] = settings
|
|
157
|
+
if active is not None:
|
|
158
|
+
hook_data["active"] = active
|
|
159
|
+
|
|
160
|
+
updated_hook: Hook = await client.update_part_hook(hook_id, hook_data)
|
|
161
|
+
return updated_hook
|
|
162
|
+
|
|
163
|
+
@mcp.tool(
|
|
164
|
+
description="List hook execution logs. Use this to debug hook executions, monitor performance, and troubleshoot errors. Logs are retained for 7 days. Returns at most 100 logs per call."
|
|
165
|
+
)
|
|
166
|
+
async def list_hook_logs(
|
|
167
|
+
hook_id: int | None = None,
|
|
168
|
+
queue_id: int | None = None,
|
|
169
|
+
annotation_id: int | None = None,
|
|
170
|
+
email_id: int | None = None,
|
|
171
|
+
log_level: Literal["INFO", "ERROR", "WARNING"] | None = None,
|
|
172
|
+
status: str | None = None,
|
|
173
|
+
status_code: int | None = None,
|
|
174
|
+
request_id: str | None = None,
|
|
175
|
+
timestamp_before: Timestamp | None = None,
|
|
176
|
+
timestamp_after: Timestamp | None = None,
|
|
177
|
+
start_before: Timestamp | None = None,
|
|
178
|
+
start_after: Timestamp | None = None,
|
|
179
|
+
end_before: Timestamp | None = None,
|
|
180
|
+
end_after: Timestamp | None = None,
|
|
181
|
+
search: str | None = None,
|
|
182
|
+
page_size: int | None = None,
|
|
183
|
+
) -> list[HookRunData]:
|
|
184
|
+
filter_mapping: dict[str, Any] = {
|
|
185
|
+
"hook": hook_id,
|
|
186
|
+
"queue": queue_id,
|
|
187
|
+
"annotation": annotation_id,
|
|
188
|
+
"email": email_id,
|
|
189
|
+
"log_level": log_level,
|
|
190
|
+
"status": status,
|
|
191
|
+
"status_code": status_code,
|
|
192
|
+
"request_id": request_id,
|
|
193
|
+
"timestamp_before": timestamp_before,
|
|
194
|
+
"timestamp_after": timestamp_after,
|
|
195
|
+
"start_before": start_before,
|
|
196
|
+
"start_after": start_after,
|
|
197
|
+
"end_before": end_before,
|
|
198
|
+
"end_after": end_after,
|
|
199
|
+
"search": search,
|
|
200
|
+
"page_size": page_size,
|
|
201
|
+
}
|
|
202
|
+
filters = {k: v for k, v in filter_mapping.items() if v is not None}
|
|
203
|
+
|
|
204
|
+
# list_hook_run_data is available from ds-feat-hook-logs branch
|
|
205
|
+
return [
|
|
206
|
+
log
|
|
207
|
+
async for log in client.list_hook_run_data(**filters) # type: ignore[attr-defined]
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
@mcp.tool(
|
|
211
|
+
description="List available hook templates from Rossum Store. Hook templates provide pre-built extension configurations (e.g., data validation, field mapping, notifications) that can be used to quickly create hooks instead of writing code from scratch. Use list_hook_templates first to find a suitable template, then use create_hook_from_template to create a hook based on that template."
|
|
212
|
+
)
|
|
213
|
+
async def list_hook_templates() -> list[HookTemplate]:
|
|
214
|
+
templates: list[HookTemplate] = []
|
|
215
|
+
async for item in client.request_paginated("hook_templates"):
|
|
216
|
+
url = item["url"]
|
|
217
|
+
templates.append(
|
|
218
|
+
HookTemplate(
|
|
219
|
+
id=int(url.split("/")[-1]),
|
|
220
|
+
url=url,
|
|
221
|
+
name=item["name"],
|
|
222
|
+
description=item.get("description", ""),
|
|
223
|
+
type=item["type"],
|
|
224
|
+
events=[],
|
|
225
|
+
config={},
|
|
226
|
+
settings_schema=item.get("settings_schema"),
|
|
227
|
+
guide="<truncated>",
|
|
228
|
+
use_token_owner=item.get("use_token_owner", False),
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
return templates
|
|
232
|
+
|
|
233
|
+
# NOTE: We explicitly document token_owner restrictions in the tool description because
|
|
234
|
+
# Sonnet 4.5 respects tool descriptions more reliably than instructions in system prompts.
|
|
235
|
+
@mcp.tool(
|
|
236
|
+
description="Create a hook from a Rossum Store template. Uses pre-built configurations from the Rossum Store. The 'events' parameter is optional and can override template defaults. If the template has 'use_token_owner=True', a valid 'token_owner' user URL is required - use list_users to find one. CRITICAL RESTRICTION: organization_group_admin users are FORBIDDEN as token_owner - the API returns HTTP 400 error."
|
|
237
|
+
)
|
|
238
|
+
async def create_hook_from_template(
|
|
239
|
+
name: str,
|
|
240
|
+
hook_template_id: int,
|
|
241
|
+
queues: list[str],
|
|
242
|
+
events: list[str] | None = None,
|
|
243
|
+
token_owner: str | None = None,
|
|
244
|
+
) -> Hook | dict:
|
|
245
|
+
if not is_read_write_mode():
|
|
246
|
+
return {"error": "create_hook_from_template is not available in read-only mode"}
|
|
247
|
+
|
|
248
|
+
logger.debug(f"Creating hook from template: name={name}, template_id={hook_template_id}")
|
|
249
|
+
|
|
250
|
+
# Build the hook template URL and fetch template to get its config
|
|
251
|
+
hook_template_url = f"{client._http_client.base_url.rstrip('/')}/hook_templates/{hook_template_id}"
|
|
252
|
+
|
|
253
|
+
hook_data: dict[str, Any] = {"name": name, "hook_template": hook_template_url, "queues": queues}
|
|
254
|
+
if events is not None:
|
|
255
|
+
hook_data["events"] = events
|
|
256
|
+
if token_owner is not None:
|
|
257
|
+
hook_data["token_owner"] = token_owner
|
|
258
|
+
|
|
259
|
+
result = await client._http_client.request_json("POST", "hooks/create", json=hook_data)
|
|
260
|
+
|
|
261
|
+
# Return the created hook
|
|
262
|
+
if hook_id := result.get("id"):
|
|
263
|
+
hook: Hook = await client.retrieve_hook(hook_id)
|
|
264
|
+
return hook
|
|
265
|
+
return {"error": "Hook wasn't likely created. Hook ID not available."}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Queue tools for Rossum MCP Server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from rossum_api import APIClientError
|
|
10
|
+
from rossum_api.domain_logic.resources import Resource
|
|
11
|
+
from rossum_api.models import deserialize_default
|
|
12
|
+
from rossum_api.models.engine import Engine # noqa: TC002 - needed at runtime for FastMCP
|
|
13
|
+
from rossum_api.models.queue import Queue # noqa: TC002 - needed at runtime for FastMCP
|
|
14
|
+
from rossum_api.models.schema import Schema # noqa: TC002 - needed at runtime for FastMCP
|
|
15
|
+
|
|
16
|
+
from rossum_mcp.tools.base import build_resource_url, is_read_write_mode
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from fastmcp import FastMCP
|
|
20
|
+
from rossum_api import AsyncRossumAPIClient
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def register_queue_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None: # noqa: C901
|
|
26
|
+
"""Register queue-related tools with the FastMCP server."""
|
|
27
|
+
|
|
28
|
+
@mcp.tool(description="Retrieve queue details.")
|
|
29
|
+
async def get_queue(queue_id: int) -> Queue:
|
|
30
|
+
"""Retrieve queue details."""
|
|
31
|
+
logger.debug(f"Retrieving queue: queue_id={queue_id}")
|
|
32
|
+
queue: Queue = await client.retrieve_queue(queue_id)
|
|
33
|
+
return queue
|
|
34
|
+
|
|
35
|
+
@mcp.tool(description="Retrieve queue schema.")
|
|
36
|
+
async def get_queue_schema(queue_id: int) -> Schema:
|
|
37
|
+
"""Retrieve complete schema for a queue."""
|
|
38
|
+
logger.debug(f"Retrieving queue schema: queue_id={queue_id}")
|
|
39
|
+
queue: Queue = await client.retrieve_queue(queue_id)
|
|
40
|
+
schema_url = queue.schema
|
|
41
|
+
schema_id = int(schema_url.rstrip("/").split("/")[-1])
|
|
42
|
+
schema: Schema = await client.retrieve_schema(schema_id)
|
|
43
|
+
return schema
|
|
44
|
+
|
|
45
|
+
@mcp.tool(description="Retrieve queue engine. Returns None if no engine assigned.")
|
|
46
|
+
async def get_queue_engine(queue_id: int) -> Engine | dict:
|
|
47
|
+
"""Retrieve complete engine information for a queue."""
|
|
48
|
+
logger.debug(f"Retrieving queue engine: queue_id={queue_id}")
|
|
49
|
+
queue: Queue = await client.retrieve_queue(queue_id)
|
|
50
|
+
|
|
51
|
+
engine_url = None
|
|
52
|
+
if queue.dedicated_engine:
|
|
53
|
+
engine_url = queue.dedicated_engine
|
|
54
|
+
elif queue.generic_engine:
|
|
55
|
+
engine_url = queue.generic_engine
|
|
56
|
+
elif queue.engine:
|
|
57
|
+
engine_url = queue.engine
|
|
58
|
+
|
|
59
|
+
if not engine_url:
|
|
60
|
+
return {"message": "No engine assigned to this queue"}
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
if isinstance(engine_url, str):
|
|
64
|
+
engine_id = int(engine_url.rstrip("/").split("/")[-1])
|
|
65
|
+
engine: Engine = await client.retrieve_engine(engine_id)
|
|
66
|
+
else:
|
|
67
|
+
engine = deserialize_default(Resource.Engine, engine_url)
|
|
68
|
+
except APIClientError as e:
|
|
69
|
+
if e.status_code == 404:
|
|
70
|
+
return {"message": f"Engine not found (engine URL: {engine_url})"}
|
|
71
|
+
raise
|
|
72
|
+
|
|
73
|
+
return engine
|
|
74
|
+
|
|
75
|
+
@mcp.tool(description="Create a queue.")
|
|
76
|
+
async def create_queue(
|
|
77
|
+
name: str,
|
|
78
|
+
workspace_id: int,
|
|
79
|
+
schema_id: int,
|
|
80
|
+
engine_id: int | None = None,
|
|
81
|
+
inbox_id: int | None = None,
|
|
82
|
+
connector_id: int | None = None,
|
|
83
|
+
locale: str = "en_GB",
|
|
84
|
+
automation_enabled: bool = False,
|
|
85
|
+
automation_level: str = "never",
|
|
86
|
+
training_enabled: bool = True,
|
|
87
|
+
splitting_screen_feature_flag: bool = False,
|
|
88
|
+
) -> Queue | dict:
|
|
89
|
+
"""Create a new queue with schema and optional engine assignment."""
|
|
90
|
+
if not is_read_write_mode():
|
|
91
|
+
return {"error": "create_queue is not available in read-only mode"}
|
|
92
|
+
|
|
93
|
+
logger.debug(
|
|
94
|
+
f"Creating queue: name={name}, workspace_id={workspace_id}, schema_id={schema_id}, engine_id={engine_id}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
queue_data: dict = {
|
|
98
|
+
"name": name,
|
|
99
|
+
"workspace": build_resource_url("workspaces", workspace_id),
|
|
100
|
+
"schema": build_resource_url("schemas", schema_id),
|
|
101
|
+
"locale": locale,
|
|
102
|
+
"automation_enabled": automation_enabled,
|
|
103
|
+
"automation_level": automation_level,
|
|
104
|
+
"training_enabled": training_enabled,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if engine_id is not None:
|
|
108
|
+
queue_data["engine"] = build_resource_url("engines", engine_id)
|
|
109
|
+
if inbox_id is not None:
|
|
110
|
+
queue_data["inbox"] = build_resource_url("inboxes", inbox_id)
|
|
111
|
+
if connector_id is not None:
|
|
112
|
+
queue_data["connector"] = build_resource_url("connectors", connector_id)
|
|
113
|
+
if splitting_screen_feature_flag:
|
|
114
|
+
if os.environ.get("SPLITTING_SCREEN_FLAG_NAME") and os.environ.get("SPLITTING_SCREEN_FLAG_VALUE"):
|
|
115
|
+
queue_data["settings"] = {
|
|
116
|
+
os.environ["SPLITTING_SCREEN_FLAG_NAME"]: os.environ["SPLITTING_SCREEN_FLAG_VALUE"]
|
|
117
|
+
}
|
|
118
|
+
else:
|
|
119
|
+
logger.error("Splitting screen failed to update")
|
|
120
|
+
|
|
121
|
+
queue: Queue = await client.create_new_queue(queue_data)
|
|
122
|
+
return queue
|
|
123
|
+
|
|
124
|
+
@mcp.tool(description="Update queue settings.")
|
|
125
|
+
async def update_queue(queue_id: int, queue_data: dict) -> Queue | dict:
|
|
126
|
+
"""Update an existing queue with new settings."""
|
|
127
|
+
if not is_read_write_mode():
|
|
128
|
+
return {"error": "update_queue is not available in read-only mode"}
|
|
129
|
+
|
|
130
|
+
logger.debug(f"Updating queue: queue_id={queue_id}, data={queue_data}")
|
|
131
|
+
updated_queue_data = await client._http_client.update(Resource.Queue, queue_id, queue_data)
|
|
132
|
+
updated_queue: Queue = client._deserializer(Resource.Queue, updated_queue_data)
|
|
133
|
+
return updated_queue
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Relation tools for Rossum MCP Server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from rossum_api.domain_logic.resources import Resource
|
|
9
|
+
from rossum_api.models.relation import (
|
|
10
|
+
Relation, # noqa: TC002 - needed at runtime for FastMCP
|
|
11
|
+
RelationType, # noqa: TC002 - needed at runtime for FastMCP
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from fastmcp import FastMCP
|
|
16
|
+
from rossum_api import AsyncRossumAPIClient
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def register_relation_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
|
|
22
|
+
"""Register relation-related tools with the FastMCP server."""
|
|
23
|
+
|
|
24
|
+
@mcp.tool(description="Retrieve relation details.")
|
|
25
|
+
async def get_relation(relation_id: int) -> Relation:
|
|
26
|
+
"""Retrieve relation details."""
|
|
27
|
+
logger.debug(f"Retrieving relation: relation_id={relation_id}")
|
|
28
|
+
relation_data = await client._http_client.fetch_one(Resource.Relation, relation_id)
|
|
29
|
+
relation_obj: Relation = client._deserializer(Resource.Relation, relation_data)
|
|
30
|
+
return relation_obj
|
|
31
|
+
|
|
32
|
+
@mcp.tool(
|
|
33
|
+
description="List all relations with optional filters. Relations introduce common relations between annotations (edit, attachment, duplicate)."
|
|
34
|
+
)
|
|
35
|
+
async def list_relations(
|
|
36
|
+
id: int | None = None,
|
|
37
|
+
type: RelationType | None = None,
|
|
38
|
+
parent: int | None = None,
|
|
39
|
+
key: str | None = None,
|
|
40
|
+
annotation: int | None = None,
|
|
41
|
+
) -> list[Relation]:
|
|
42
|
+
"""List all relations with optional filters."""
|
|
43
|
+
logger.debug(f"Listing relations: id={id}, type={type}, parent={parent}, key={key}, annotation={annotation}")
|
|
44
|
+
filters: dict[str, int | str] = {}
|
|
45
|
+
if id is not None:
|
|
46
|
+
filters["id"] = id
|
|
47
|
+
if type is not None:
|
|
48
|
+
filters["type"] = type
|
|
49
|
+
if parent is not None:
|
|
50
|
+
filters["parent"] = parent
|
|
51
|
+
if key is not None:
|
|
52
|
+
filters["key"] = key
|
|
53
|
+
if annotation is not None:
|
|
54
|
+
filters["annotation"] = annotation
|
|
55
|
+
|
|
56
|
+
return [relation async for relation in client.list_relations(**filters)] # type: ignore[arg-type]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Rule tools for Rossum MCP Server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from rossum_api.models.rule import Rule # noqa: TC002 - needed at runtime for FastMCP
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
from rossum_api import AsyncRossumAPIClient
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_rule_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
|
|
18
|
+
"""Register rule-related tools with the FastMCP server."""
|
|
19
|
+
|
|
20
|
+
@mcp.tool(description="Retrieve rule details.")
|
|
21
|
+
async def get_rule(rule_id: int) -> Rule:
|
|
22
|
+
"""Retrieve rule details."""
|
|
23
|
+
logger.debug(f"Retrieving rule: rule_id={rule_id}")
|
|
24
|
+
rule: Rule = await client.retrieve_rule(rule_id)
|
|
25
|
+
return rule
|
|
26
|
+
|
|
27
|
+
@mcp.tool(description="List all rules.")
|
|
28
|
+
async def list_rules(
|
|
29
|
+
schema_id: int | None = None, organization_id: int | None = None, enabled: bool | None = None
|
|
30
|
+
) -> list[Rule]:
|
|
31
|
+
"""List all rules, optionally filtered by schema, organization, and enabled status."""
|
|
32
|
+
logger.debug(f"Listing rules: schema_id={schema_id}, organization_id={organization_id}, enabled={enabled}")
|
|
33
|
+
filters: dict = {}
|
|
34
|
+
if schema_id is not None:
|
|
35
|
+
filters["schema"] = schema_id
|
|
36
|
+
if organization_id is not None:
|
|
37
|
+
filters["organization"] = organization_id
|
|
38
|
+
if enabled is not None:
|
|
39
|
+
filters["enabled"] = enabled
|
|
40
|
+
|
|
41
|
+
rules_list: list[Rule] = [rule async for rule in client.list_rules(**filters)]
|
|
42
|
+
return rules_list
|