rossum-mcp 0.3.5__py3-none-any.whl → 1.0.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 CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.3.5"
3
+ __version__ = "1.0.0"
rossum_mcp/server.py CHANGED
@@ -17,6 +17,7 @@ from rossum_api.dtos import Token
17
17
  from rossum_mcp.logging_config import setup_logging
18
18
  from rossum_mcp.tools import (
19
19
  register_annotation_tools,
20
+ register_discovery_tools,
20
21
  register_document_relation_tools,
21
22
  register_email_template_tools,
22
23
  register_engine_tools,
@@ -45,6 +46,7 @@ logger.info(f"Rossum MCP Server starting in {MODE} mode")
45
46
  mcp = FastMCP("rossum-mcp-server")
46
47
  client = AsyncRossumAPIClient(base_url=BASE_URL, credentials=Token(token=API_TOKEN))
47
48
 
49
+ register_discovery_tools(mcp)
48
50
  register_annotation_tools(mcp, client)
49
51
  register_queue_tools(mcp, client)
50
52
  register_schema_tools(mcp, client)
@@ -3,6 +3,13 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from rossum_mcp.tools.annotations import register_annotation_tools
6
+ from rossum_mcp.tools.catalog import (
7
+ TOOL_CATALOG,
8
+ ToolCategory,
9
+ ToolInfo,
10
+ get_catalog_summary,
11
+ )
12
+ from rossum_mcp.tools.discovery import register_discovery_tools
6
13
  from rossum_mcp.tools.document_relations import register_document_relation_tools
7
14
  from rossum_mcp.tools.email_templates import register_email_template_tools
8
15
  from rossum_mcp.tools.engines import register_engine_tools
@@ -15,7 +22,12 @@ from rossum_mcp.tools.users import register_user_tools
15
22
  from rossum_mcp.tools.workspaces import register_workspace_tools
16
23
 
17
24
  __all__ = [
25
+ "TOOL_CATALOG",
26
+ "ToolCategory",
27
+ "ToolInfo",
28
+ "get_catalog_summary",
18
29
  "register_annotation_tools",
30
+ "register_discovery_tools",
19
31
  "register_document_relation_tools",
20
32
  "register_email_template_tools",
21
33
  "register_engine_tools",
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Literal
9
9
 
10
10
  from rossum_api.models.annotation import Annotation
11
11
 
12
- from rossum_mcp.tools.base import is_read_write_mode
12
+ from rossum_mcp.tools.base import delete_resource, is_read_write_mode
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from fastmcp import FastMCP
@@ -130,6 +130,12 @@ async def _confirm_annotation(client: AsyncRossumAPIClient, annotation_id: int)
130
130
  }
131
131
 
132
132
 
133
+ async def _delete_annotation(client: AsyncRossumAPIClient, annotation_id: int) -> dict:
134
+ return await delete_resource(
135
+ "annotation", annotation_id, client.delete_annotation, f"Annotation {annotation_id} moved to 'deleted' status"
136
+ )
137
+
138
+
133
139
  def register_annotation_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
134
140
  """Register annotation-related tools with the FastMCP server."""
135
141
 
@@ -165,3 +171,7 @@ def register_annotation_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> Non
165
171
  )
166
172
  async def confirm_annotation(annotation_id: int) -> dict:
167
173
  return await _confirm_annotation(client, annotation_id)
174
+
175
+ @mcp.tool(description="Delete an annotation. Moves to 'deleted' status (soft delete).")
176
+ async def delete_annotation(annotation_id: int) -> dict:
177
+ return await _delete_annotation(client, annotation_id)
rossum_mcp/tools/base.py CHANGED
@@ -2,12 +2,16 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
5
6
  import os
6
7
  from typing import TYPE_CHECKING
7
8
 
8
9
  if TYPE_CHECKING:
10
+ from collections.abc import Awaitable, Callable
9
11
  from typing import Any
10
12
 
13
+ logger = logging.getLogger(__name__)
14
+
11
15
  BASE_URL = os.environ.get("ROSSUM_API_BASE_URL", "").rstrip("/")
12
16
  MODE = os.environ.get("ROSSUM_MCP_MODE", "read-write").lower()
13
17
 
@@ -38,3 +42,32 @@ def truncate_dict_fields(data: dict[str, Any], fields: tuple[str, ...]) -> dict[
38
42
  if field in result:
39
43
  result[field] = TRUNCATED_MARKER
40
44
  return result
45
+
46
+
47
+ async def delete_resource(
48
+ resource_type: str,
49
+ resource_id: int,
50
+ delete_fn: Callable[[int], Awaitable[None]],
51
+ success_message: str | None = None,
52
+ ) -> dict:
53
+ """Generic delete operation with read-only mode check.
54
+
55
+ Args:
56
+ resource_type: Name of the resource (e.g., "queue", "workspace")
57
+ resource_id: ID of the resource to delete
58
+ delete_fn: Async function that performs the deletion
59
+ success_message: Custom success message. If None, uses default format.
60
+
61
+ Returns:
62
+ Dict with "message" on success or "error" in read-only mode.
63
+ """
64
+ tool_name = f"delete_{resource_type}"
65
+ if not is_read_write_mode():
66
+ return {"error": f"{tool_name} is not available in read-only mode"}
67
+
68
+ logger.debug(f"Deleting {resource_type}: {resource_type}_id={resource_id}")
69
+ await delete_fn(resource_id)
70
+
71
+ if success_message is None:
72
+ success_message = f"{resource_type.title()} {resource_id} deleted successfully"
73
+ return {"message": success_message}
@@ -0,0 +1,176 @@
1
+ """Tool catalog for dynamic tool discovery.
2
+
3
+ Provides lightweight metadata for all MCP tools organized by category.
4
+ This is the single source of truth for tool categorization - the agent
5
+ fetches this catalog from MCP to avoid data duplication.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+
12
+
13
+ @dataclass
14
+ class ToolInfo:
15
+ """Lightweight tool metadata for catalog."""
16
+
17
+ name: str
18
+ description: str
19
+ destructive: bool = False
20
+
21
+
22
+ @dataclass
23
+ class ToolCategory:
24
+ """A category of related tools."""
25
+
26
+ name: str
27
+ description: str
28
+ tools: list[ToolInfo]
29
+ keywords: list[str]
30
+
31
+
32
+ # Tool catalog organized by functional category
33
+ # Keywords enable automatic pre-loading based on user request text
34
+ TOOL_CATALOG: dict[str, ToolCategory] = {
35
+ "annotations": ToolCategory(
36
+ name="annotations",
37
+ description="Document processing: upload, retrieve, update, and confirm annotations",
38
+ tools=[
39
+ ToolInfo("upload_document", "Upload document to queue"),
40
+ ToolInfo("get_annotation", "Retrieve annotation with extracted data"),
41
+ ToolInfo("list_annotations", "List annotations for a queue"),
42
+ ToolInfo("start_annotation", "Start annotation (to_review -> reviewing)"),
43
+ ToolInfo("bulk_update_annotation_fields", "Bulk update annotation fields"),
44
+ ToolInfo("confirm_annotation", "Confirm annotation (-> confirmed)"),
45
+ ToolInfo("delete_annotation", "Delete annotation (soft delete)", destructive=True),
46
+ ],
47
+ keywords=["annotation", "document", "upload", "extract", "confirm", "review"],
48
+ ),
49
+ "queues": ToolCategory(
50
+ name="queues",
51
+ description="Queue management: create, configure, and list document processing queues",
52
+ tools=[
53
+ ToolInfo("get_queue", "Retrieve queue details"),
54
+ ToolInfo("list_queues", "List all queues"),
55
+ ToolInfo("get_queue_schema", "Get queue's schema"),
56
+ ToolInfo("get_queue_engine", "Get queue's AI engine"),
57
+ ToolInfo("create_queue", "Create a queue"),
58
+ ToolInfo("update_queue", "Update queue settings"),
59
+ ToolInfo("get_queue_template_names", "List available queue templates"),
60
+ ToolInfo("create_queue_from_template", "Create queue from template"),
61
+ ToolInfo("delete_queue", "Delete queue (24h delayed)", destructive=True),
62
+ ],
63
+ keywords=["queue", "inbox", "connector"],
64
+ ),
65
+ "schemas": ToolCategory(
66
+ name="schemas",
67
+ description="Schema management: define and modify document field structures",
68
+ tools=[
69
+ ToolInfo("get_schema", "Retrieve schema details"),
70
+ ToolInfo("list_schemas", "List all schemas"),
71
+ ToolInfo("update_schema", "Update schema"),
72
+ ToolInfo("create_schema", "Create new schema"),
73
+ ToolInfo("patch_schema", "Add/update/remove schema fields"),
74
+ ToolInfo("get_schema_tree_structure", "Get lightweight schema tree"),
75
+ ToolInfo("prune_schema_fields", "Bulk remove schema fields"),
76
+ ToolInfo("delete_schema", "Delete schema", destructive=True),
77
+ ],
78
+ keywords=["schema", "field", "datapoint", "section", "multivalue", "tuple"],
79
+ ),
80
+ "engines": ToolCategory(
81
+ name="engines",
82
+ description="AI engine management: create and configure extraction/splitting engines",
83
+ tools=[
84
+ ToolInfo("get_engine", "Retrieve engine details"),
85
+ ToolInfo("list_engines", "List all engines"),
86
+ ToolInfo("update_engine", "Update engine settings"),
87
+ ToolInfo("create_engine", "Create new engine"),
88
+ ToolInfo("create_engine_field", "Create engine field mapping"),
89
+ ToolInfo("get_engine_fields", "List engine fields"),
90
+ ],
91
+ keywords=["engine", "ai", "extractor", "splitter", "training"],
92
+ ),
93
+ "hooks": ToolCategory(
94
+ name="hooks",
95
+ description="Extensions/webhooks: create and manage automation hooks",
96
+ tools=[
97
+ ToolInfo("get_hook", "Retrieve hook details with code"),
98
+ ToolInfo("list_hooks", "List all hooks for a queue"),
99
+ ToolInfo("create_hook", "Create new hook"),
100
+ ToolInfo("update_hook", "Update hook configuration"),
101
+ ToolInfo("list_hook_logs", "View hook execution logs"),
102
+ ToolInfo("list_hook_templates", "List Rossum Store templates"),
103
+ ToolInfo("create_hook_from_template", "Create hook from template"),
104
+ ToolInfo("delete_hook", "Delete hook", destructive=True),
105
+ ],
106
+ keywords=["hook", "extension", "webhook", "automation", "function", "serverless"],
107
+ ),
108
+ "email_templates": ToolCategory(
109
+ name="email_templates",
110
+ description="Email templates: configure automated email responses",
111
+ tools=[
112
+ ToolInfo("get_email_template", "Retrieve email template"),
113
+ ToolInfo("list_email_templates", "List email templates"),
114
+ ToolInfo("create_email_template", "Create email template"),
115
+ ],
116
+ keywords=["email", "template", "notification", "rejection"],
117
+ ),
118
+ "document_relations": ToolCategory(
119
+ name="document_relations",
120
+ description="Document relations: manage export/einvoice document links",
121
+ tools=[
122
+ ToolInfo("get_document_relation", "Retrieve document relation"),
123
+ ToolInfo("list_document_relations", "List document relations"),
124
+ ],
125
+ keywords=["document relation", "export", "einvoice"],
126
+ ),
127
+ "relations": ToolCategory(
128
+ name="relations",
129
+ description="Annotation relations: manage edit/attachment/duplicate links",
130
+ tools=[
131
+ ToolInfo("get_relation", "Retrieve relation details"),
132
+ ToolInfo("list_relations", "List annotation relations"),
133
+ ],
134
+ keywords=["relation", "duplicate", "attachment"],
135
+ ),
136
+ "rules": ToolCategory(
137
+ name="rules",
138
+ description="Validation rules: manage schema validation rules",
139
+ tools=[
140
+ ToolInfo("get_rule", "Retrieve rule details"),
141
+ ToolInfo("list_rules", "List validation rules"),
142
+ ToolInfo("delete_rule", "Delete rule", destructive=True),
143
+ ],
144
+ keywords=["rule", "validation", "constraint"],
145
+ ),
146
+ "users": ToolCategory(
147
+ name="users",
148
+ description="User management: list users and roles",
149
+ tools=[
150
+ ToolInfo("get_user", "Retrieve user details"),
151
+ ToolInfo("list_users", "List users with filters"),
152
+ ToolInfo("list_user_roles", "List available user roles"),
153
+ ],
154
+ keywords=["user", "role", "permission", "token_owner"],
155
+ ),
156
+ "workspaces": ToolCategory(
157
+ name="workspaces",
158
+ description="Workspace management: organize queues into workspaces",
159
+ tools=[
160
+ ToolInfo("get_workspace", "Retrieve workspace details"),
161
+ ToolInfo("list_workspaces", "List all workspaces"),
162
+ ToolInfo("create_workspace", "Create new workspace"),
163
+ ToolInfo("delete_workspace", "Delete workspace", destructive=True),
164
+ ],
165
+ keywords=["workspace", "organization"],
166
+ ),
167
+ }
168
+
169
+
170
+ def get_catalog_summary() -> str:
171
+ """Get a compact text summary of all tool categories for the system prompt."""
172
+ lines = ["Available MCP tool categories (use `list_tool_categories` for details):"]
173
+ for category in TOOL_CATALOG.values():
174
+ tool_names = ", ".join(t.name for t in category.tools)
175
+ lines.append(f"- **{category.name}**: {category.description} [{tool_names}]")
176
+ return "\n".join(lines)
@@ -0,0 +1,37 @@
1
+ """Discovery tools for dynamic tool loading.
2
+
3
+ Provides MCP tool to explore available tool categories and their metadata.
4
+ The agent uses this to fetch the catalog and load tools on-demand.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import asdict
10
+ from typing import TYPE_CHECKING
11
+
12
+ from rossum_mcp.tools.catalog import TOOL_CATALOG
13
+
14
+ if TYPE_CHECKING:
15
+ from fastmcp import FastMCP
16
+
17
+
18
+ def register_discovery_tools(mcp: FastMCP) -> None:
19
+ """Register discovery tools with the FastMCP server."""
20
+
21
+ @mcp.tool(
22
+ description="List all available tool categories with descriptions, tool names, and keywords. "
23
+ "Use this to discover what tools are available, then use load_tool_category to load "
24
+ "tools from specific categories before using them. Tools with destructive=true require "
25
+ "explicit user request to use (delete operations)."
26
+ )
27
+ async def list_tool_categories() -> list[dict]:
28
+ return [
29
+ {
30
+ "name": category.name,
31
+ "description": category.description,
32
+ "tool_count": len(category.tools),
33
+ "tools": [asdict(tool) for tool in category.tools],
34
+ "keywords": category.keywords,
35
+ }
36
+ for category in TOOL_CATALOG.values()
37
+ ]
@@ -38,15 +38,11 @@ async def _list_email_templates(
38
38
  if name is not None:
39
39
  filters["name"] = name
40
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)]
41
+ templates_list: list[EmailTemplate] = []
42
+ async for template in client.list_email_templates(**filters):
43
+ templates_list.append(template)
44
+ if first_n is not None and len(templates_list) >= first_n:
45
+ break
50
46
 
51
47
  return templates_list
52
48
 
rossum_mcp/tools/hooks.py CHANGED
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Annotated, Any, Literal
8
8
 
9
9
  from rossum_api.models.hook import Hook, HookRunData, HookType
10
10
 
11
- from rossum_mcp.tools.base import TRUNCATED_MARKER, is_read_write_mode
11
+ from rossum_mcp.tools.base import TRUNCATED_MARKER, delete_resource, is_read_write_mode
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from fastmcp import FastMCP
@@ -56,15 +56,11 @@ async def _list_hooks(
56
56
  if active is not None:
57
57
  filters["active"] = active
58
58
 
59
- if first_n is not None:
60
- hooks_iter = client.list_hooks(**filters)
61
- hooks_list: list[Hook] = []
62
- n = 0
63
- while n < first_n:
64
- hooks_list.append(await anext(hooks_iter))
65
- n += 1
66
- else:
67
- hooks_list = [hook async for hook in client.list_hooks(**filters)]
59
+ hooks_list: list[Hook] = []
60
+ async for hook in client.list_hooks(**filters):
61
+ hooks_list.append(hook)
62
+ if first_n is not None and len(hooks_list) >= first_n:
63
+ break
68
64
 
69
65
  return hooks_list
70
66
 
@@ -238,6 +234,10 @@ async def _create_hook_from_template(
238
234
  return {"error": "Hook wasn't likely created. Hook ID not available."}
239
235
 
240
236
 
237
+ async def _delete_hook(client: AsyncRossumAPIClient, hook_id: int) -> dict:
238
+ return await delete_resource("hook", hook_id, client.delete_hook)
239
+
240
+
241
241
  def register_hook_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
242
242
  """Register hook-related tools with the FastMCP server."""
243
243
 
@@ -256,7 +256,7 @@ def register_hook_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
256
256
  return await _list_hooks(client, queue_id, active, first_n)
257
257
 
258
258
  @mcp.tool(
259
- description="Create a new hook. If token_owner is provided, organization_group_admin users CANNOT be used (API will reject)."
259
+ description="Create a new hook. For function hooks: 'source' in config is auto-renamed to 'function', runtime defaults to 'python3.12', timeout_s is capped at 60s. If token_owner is provided, organization_group_admin users CANNOT be used."
260
260
  )
261
261
  async def create_hook(
262
262
  name: str,
@@ -341,3 +341,7 @@ def register_hook_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
341
341
  token_owner: str | None = None,
342
342
  ) -> Hook | dict:
343
343
  return await _create_hook_from_template(client, name, hook_template_id, queues, events, token_owner)
344
+
345
+ @mcp.tool(description="Delete a hook.")
346
+ async def delete_hook(hook_id: int) -> dict:
347
+ return await _delete_hook(client, hook_id)
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import logging
6
6
  import os
7
7
  from dataclasses import replace
8
- from typing import TYPE_CHECKING, cast
8
+ from typing import TYPE_CHECKING, Literal, cast, get_args
9
9
 
10
10
  from rossum_api import APIClientError
11
11
  from rossum_api.domain_logic.resources import Resource
@@ -14,7 +14,7 @@ from rossum_api.models.engine import Engine
14
14
  from rossum_api.models.queue import Queue
15
15
  from rossum_api.models.schema import Schema
16
16
 
17
- from rossum_mcp.tools.base import build_resource_url, is_read_write_mode, truncate_dict_fields
17
+ from rossum_mcp.tools.base import build_resource_url, delete_resource, is_read_write_mode, truncate_dict_fields
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from fastmcp import FastMCP
@@ -46,10 +46,15 @@ async def _get_queue(client: AsyncRossumAPIClient, queue_id: int) -> Queue:
46
46
 
47
47
 
48
48
  async def _list_queues(
49
- client: AsyncRossumAPIClient, workspace_id: int | None = None, name: str | None = None
49
+ client: AsyncRossumAPIClient,
50
+ id: str | None = None,
51
+ workspace_id: int | None = None,
52
+ name: str | None = None,
50
53
  ) -> list[Queue]:
51
- logger.debug(f"Listing queues: workspace_id={workspace_id}, name={name}")
54
+ logger.debug(f"Listing queues: id={id}, workspace_id={workspace_id}, name={name}")
52
55
  filters: dict[str, int | str] = {}
56
+ if id is not None:
57
+ filters["id"] = id
53
58
  if workspace_id is not None:
54
59
  filters["workspace"] = workspace_id
55
60
  if name is not None:
@@ -155,8 +160,14 @@ async def _update_queue(client: AsyncRossumAPIClient, queue_id: int, queue_data:
155
160
  return cast("Queue", client._deserializer(Resource.Queue, updated_queue_data))
156
161
 
157
162
 
163
+ async def _delete_queue(client: AsyncRossumAPIClient, queue_id: int) -> dict:
164
+ return await delete_resource(
165
+ "queue", queue_id, client.delete_queue, f"Queue {queue_id} scheduled for deletion (starts after 24 hours)"
166
+ )
167
+
168
+
158
169
  # Available template names for create_queue_from_template
159
- QUEUE_TEMPLATE_NAMES = (
170
+ QueueTemplateName = Literal[
160
171
  "EU Demo Template",
161
172
  "AP&R EU Demo Template",
162
173
  "Tax Invoice EU Demo Template",
@@ -177,13 +188,14 @@ QUEUE_TEMPLATE_NAMES = (
177
188
  "Credit Note Demo Template",
178
189
  "Debit Note Demo Template",
179
190
  "Proforma Invoice Demo Template",
180
- )
191
+ ]
192
+ QUEUE_TEMPLATE_NAMES = get_args(QueueTemplateName)
181
193
 
182
194
 
183
195
  async def _create_queue_from_template(
184
196
  client: AsyncRossumAPIClient,
185
197
  name: str,
186
- template_name: str,
198
+ template_name: QueueTemplateName,
187
199
  workspace_id: int,
188
200
  include_documents: bool = False,
189
201
  engine_id: int | None = None,
@@ -226,9 +238,11 @@ def register_queue_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
226
238
  async def get_queue(queue_id: int) -> Queue:
227
239
  return await _get_queue(client, queue_id)
228
240
 
229
- @mcp.tool(description="List all queues with optional filters.")
230
- async def list_queues(workspace_id: int | None = None, name: str | None = None) -> list[Queue]:
231
- return await _list_queues(client, workspace_id, name)
241
+ @mcp.tool(description="List all queues with optional filters. id accepts comma-separated values (e.g. '1,2,3').")
242
+ async def list_queues(
243
+ id: str | None = None, workspace_id: int | None = None, name: str | None = None
244
+ ) -> list[Queue]:
245
+ return await _list_queues(client, id, workspace_id, name)
232
246
 
233
247
  @mcp.tool(description="Retrieve queue schema.")
234
248
  async def get_queue_schema(queue_id: int) -> Schema:
@@ -271,6 +285,12 @@ def register_queue_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
271
285
  async def update_queue(queue_id: int, queue_data: dict) -> Queue | dict:
272
286
  return await _update_queue(client, queue_id, queue_data)
273
287
 
288
+ @mcp.tool(
289
+ description="Delete a queue. Deletion starts after 24 hours. Also deletes all related objects (annotations, documents)."
290
+ )
291
+ async def delete_queue(queue_id: int) -> dict:
292
+ return await _delete_queue(client, queue_id)
293
+
274
294
  @mcp.tool(description="Get available queue template names for create_queue_from_template.")
275
295
  async def get_queue_template_names() -> list[str]:
276
296
  return list(QUEUE_TEMPLATE_NAMES)
@@ -281,19 +301,11 @@ def register_queue_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
281
301
  )
282
302
  async def create_queue_from_template(
283
303
  name: str,
284
- template_name: str,
304
+ template_name: QueueTemplateName,
285
305
  workspace_id: int,
286
306
  include_documents: bool = False,
287
307
  engine_id: int | None = None,
288
308
  ) -> Queue | dict:
289
- """
290
- Available templates: EU Demo Template, AP&R EU Demo Template, Tax Invoice EU Demo Template,
291
- US Demo Template, AP&R US Demo Template, Tax Invoice US Demo Template, UK Demo Template,
292
- AP&R UK Demo Template, Tax Invoice UK Demo Template, CZ Demo Template, Empty Organization Template,
293
- Delivery Notes Demo Template, Delivery Note Demo Template, Chinese Invoices (Fapiao) Demo Template,
294
- Tax Invoice CN Demo Template, Certificates of Analysis Demo Template, Purchase Order Demo Template,
295
- Credit Note Demo Template, Debit Note Demo Template, Proforma Invoice Demo Template.
296
- """
297
309
  return await _create_queue_from_template(
298
310
  client, name, template_name, workspace_id, include_documents, engine_id
299
311
  )
rossum_mcp/tools/rules.py CHANGED
@@ -7,6 +7,8 @@ from typing import TYPE_CHECKING
7
7
 
8
8
  from rossum_api.models.rule import Rule
9
9
 
10
+ from rossum_mcp.tools.base import delete_resource
11
+
10
12
  if TYPE_CHECKING:
11
13
  from fastmcp import FastMCP
12
14
  from rossum_api import AsyncRossumAPIClient
@@ -39,6 +41,10 @@ async def _list_rules(
39
41
  return rules_list
40
42
 
41
43
 
44
+ async def _delete_rule(client: AsyncRossumAPIClient, rule_id: int) -> dict:
45
+ return await delete_resource("rule", rule_id, client.delete_rule)
46
+
47
+
42
48
  def register_rule_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
43
49
  """Register rule-related tools with the FastMCP server."""
44
50
 
@@ -51,3 +57,7 @@ def register_rule_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
51
57
  schema_id: int | None = None, organization_id: int | None = None, enabled: bool | None = None
52
58
  ) -> list[Rule]:
53
59
  return await _list_rules(client, schema_id, organization_id, enabled)
60
+
61
+ @mcp.tool(description="Delete a rule.")
62
+ async def delete_rule(rule_id: int) -> dict:
63
+ return await _delete_rule(client, rule_id)
@@ -7,10 +7,11 @@ import logging
7
7
  from dataclasses import asdict, dataclass, is_dataclass, replace
8
8
  from typing import TYPE_CHECKING, Any, Literal
9
9
 
10
+ from rossum_api import APIClientError
10
11
  from rossum_api.domain_logic.resources import Resource
11
12
  from rossum_api.models.schema import Schema
12
13
 
13
- from rossum_mcp.tools.base import TRUNCATED_MARKER, is_read_write_mode
14
+ from rossum_mcp.tools.base import TRUNCATED_MARKER, delete_resource, is_read_write_mode
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from fastmcp import FastMCP
@@ -427,9 +428,14 @@ def apply_schema_patch(
427
428
  return content
428
429
 
429
430
 
430
- async def _get_schema(client: AsyncRossumAPIClient, schema_id: int) -> Schema:
431
- schema: Schema = await client.retrieve_schema(schema_id)
432
- return schema
431
+ async def _get_schema(client: AsyncRossumAPIClient, schema_id: int) -> Schema | dict:
432
+ try:
433
+ schema: Schema = await client.retrieve_schema(schema_id)
434
+ return schema
435
+ except APIClientError as e:
436
+ if e.status_code == 404:
437
+ return {"error": f"Schema {schema_id} not found"}
438
+ raise
433
439
 
434
440
 
435
441
  @dataclass
@@ -666,8 +672,10 @@ async def _patch_schema(
666
672
  return updated_schema
667
673
 
668
674
 
669
- async def _get_schema_tree_structure(client: AsyncRossumAPIClient, schema_id: int) -> list[dict]:
675
+ async def _get_schema_tree_structure(client: AsyncRossumAPIClient, schema_id: int) -> list[dict] | dict:
670
676
  schema = await _get_schema(client, schema_id)
677
+ if isinstance(schema, dict):
678
+ return schema
671
679
  content_dicts: list[dict[str, Any]] = [
672
680
  asdict(section) if is_dataclass(section) else dict(section) # type: ignore[arg-type]
673
681
  for section in schema.content
@@ -715,11 +723,15 @@ async def _prune_schema_fields(
715
723
  return {"removed_fields": sorted(removed), "remaining_fields": sorted(remaining_ids)}
716
724
 
717
725
 
726
+ async def _delete_schema(client: AsyncRossumAPIClient, schema_id: int) -> dict:
727
+ return await delete_resource("schema", schema_id, client.delete_schema)
728
+
729
+
718
730
  def register_schema_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
719
731
  """Register schema-related tools with the FastMCP server."""
720
732
 
721
733
  @mcp.tool(description="Retrieve schema details.")
722
- async def get_schema(schema_id: int) -> Schema:
734
+ async def get_schema(schema_id: int) -> Schema | dict:
723
735
  return await _get_schema(client, schema_id)
724
736
 
725
737
  @mcp.tool(description="List all schemas with optional filters.")
@@ -737,6 +749,8 @@ def register_schema_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
737
749
  @mcp.tool(
738
750
  description="""Patch schema nodes (add/update/remove fields in a schema).
739
751
 
752
+ You MUST load `schema-patching` skill first to avoid errors.
753
+
740
754
  Operations:
741
755
  - add: Create new field. Requires parent_id (section or tuple id) and node_data.
742
756
  - update: Modify existing field. Requires node_data with fields to change.
@@ -748,7 +762,8 @@ Node types for add:
748
762
  - Multivalue (table): {"label": "Table", "category": "multivalue", "children": <tuple>}
749
763
  - Tuple (table row): {"id": "row_id", "label": "Row", "category": "tuple", "children": [<datapoints with id>]}
750
764
 
751
- Important: Datapoints inside a tuple MUST have an "id" field. Section-level datapoints get id from node_id parameter."""
765
+ Important: Datapoints inside a tuple MUST have an "id" field. Section-level datapoints get id from node_id parameter.
766
+ """
752
767
  )
753
768
  async def patch_schema(
754
769
  schema_id: int,
@@ -761,7 +776,7 @@ Important: Datapoints inside a tuple MUST have an "id" field. Section-level data
761
776
  return await _patch_schema(client, schema_id, operation, node_id, node_data, parent_id, position)
762
777
 
763
778
  @mcp.tool(description="Get lightweight tree structure of schema with only ids, labels, categories, and types.")
764
- async def get_schema_tree_structure(schema_id: int) -> list[dict]:
779
+ async def get_schema_tree_structure(schema_id: int) -> list[dict] | dict:
765
780
  return await _get_schema_tree_structure(client, schema_id)
766
781
 
767
782
  @mcp.tool(
@@ -779,3 +794,7 @@ Returns dict with removed_fields and remaining_fields lists. Sections cannot be
779
794
  fields_to_remove: list[str] | None = None,
780
795
  ) -> dict:
781
796
  return await _prune_schema_fields(client, schema_id, fields_to_keep, fields_to_remove)
797
+
798
+ @mcp.tool(description="Delete a schema. Fails if schema is linked to a queue or annotation (HTTP 409).")
799
+ async def delete_schema(schema_id: int) -> dict:
800
+ return await _delete_schema(client, schema_id)