rossum-mcp 1.0.0__tar.gz → 1.1.0__tar.gz

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.
Files changed (33) hide show
  1. {rossum_mcp-1.0.0/rossum_mcp.egg-info → rossum_mcp-1.1.0}/PKG-INFO +27 -7
  2. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/README.md +26 -6
  3. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/pyproject.toml +1 -1
  4. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/__init__.py +1 -1
  5. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/server.py +18 -5
  6. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/base.py +21 -2
  7. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/catalog.py +29 -26
  8. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/discovery.py +2 -2
  9. rossum_mcp-1.1.0/rossum_mcp/tools/rules.py +241 -0
  10. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0/rossum_mcp.egg-info}/PKG-INFO +27 -7
  11. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/tests/test_catalog.py +27 -5
  12. rossum_mcp-1.0.0/rossum_mcp/tools/rules.py +0 -63
  13. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/LICENSE +0 -0
  14. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/logging_config.py +0 -0
  15. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/py.typed +0 -0
  16. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/__init__.py +0 -0
  17. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/annotations.py +0 -0
  18. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/document_relations.py +0 -0
  19. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/email_templates.py +0 -0
  20. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/engines.py +0 -0
  21. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/hooks.py +0 -0
  22. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/queues.py +0 -0
  23. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/relations.py +0 -0
  24. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/schemas.py +0 -0
  25. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/users.py +0 -0
  26. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp/tools/workspaces.py +0 -0
  27. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp.egg-info/SOURCES.txt +0 -0
  28. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp.egg-info/dependency_links.txt +0 -0
  29. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp.egg-info/entry_points.txt +0 -0
  30. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp.egg-info/requires.txt +0 -0
  31. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/rossum_mcp.egg-info/top_level.txt +0 -0
  32. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/setup.cfg +0 -0
  33. {rossum_mcp-1.0.0 → rossum_mcp-1.1.0}/tests/test_logging_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rossum-mcp
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: MCP server for AI-powered Rossum orchestration: document workflows, debug pipelines automatically, and configure intelligent document processing through natural language.
5
5
  Author-email: "Dan Stancl (Rossum AI)" <daniel.stancl@gmail.com>
6
6
  License: MIT
@@ -51,14 +51,14 @@ Dynamic: license-file
51
51
 
52
52
  <div align="center">
53
53
 
54
- **MCP server for AI-powered Rossum document processing. 56 tools for queues, schemas, hooks, engines, and more.**
54
+ **MCP server for AI-powered Rossum document processing. 59 tools for queues, schemas, hooks, engines, and more.**
55
55
 
56
56
  [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://stancld.github.io/rossum-agents/)
57
- [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue.svg)](https://www.python.org/downloads/)
57
+ [![Python](https://img.shields.io/pypi/pyversions/rossum-mcp.svg)](https://pypi.org/project/rossum-mcp/)
58
58
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
59
  [![PyPI - rossum-mcp](https://img.shields.io/pypi/v/rossum-mcp?label=rossum-mcp)](https://pypi.org/project/rossum-mcp/)
60
60
  [![Coverage](https://codecov.io/gh/stancld/rossum-agents/branch/master/graph/badge.svg?flag=rossum-mcp)](https://codecov.io/gh/stancld/rossum-agents)
61
- [![MCP Tools](https://img.shields.io/badge/MCP_Tools-56-blue.svg)](#available-tools)
61
+ [![MCP Tools](https://img.shields.io/badge/MCP_Tools-59-blue.svg)](#available-tools)
62
62
 
63
63
  [![Rossum API](https://img.shields.io/badge/Rossum-API-orange.svg)](https://github.com/rossumai/rossum-api)
64
64
  [![MCP](https://img.shields.io/badge/MCP-compatible-green.svg)](https://modelcontextprotocol.io/)
@@ -127,9 +127,29 @@ Configure Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_c
127
127
 
128
128
  Set `ROSSUM_MCP_MODE=read-only` to disable all CREATE, UPDATE, and UPLOAD operations. Only GET and LIST operations will be available.
129
129
 
130
+ ### Runtime Mode Switching
131
+
132
+ Two tools allow dynamic mode control:
133
+
134
+ | Tool | Description |
135
+ |------|-------------|
136
+ | `get_mcp_mode` | Returns current operation mode (`read-only` or `read-write`) |
137
+ | `set_mcp_mode` | Switches between modes at runtime |
138
+
139
+ **Use case:** Start in read-only mode for safe exploration, then switch to read-write when ready to make changes.
140
+
141
+ ```
142
+ User: What mode are we in?
143
+ Assistant: [calls get_mcp_mode] → "read-only"
144
+
145
+ User: I'm ready to update the schema now.
146
+ Assistant: [calls set_mcp_mode("read-write")] → Mode switched to read-write
147
+ [calls update_schema(...)]
148
+ ```
149
+
130
150
  ## Available Tools
131
151
 
132
- The server provides **56 tools** organized into categories:
152
+ The server provides **59 tools** organized into categories:
133
153
 
134
154
  | Category | Tools | Description |
135
155
  |----------|-------|-------------|
@@ -137,7 +157,7 @@ The server provides **56 tools** organized into categories:
137
157
  | **Queue Management** | 9 | Create, configure, delete, and list queues |
138
158
  | **Schema Management** | 8 | Define, modify, and delete field structures |
139
159
  | **Engine Management** | 6 | Configure extraction and splitting engines |
140
- | **Extensions & Rules** | 11 | Webhooks, serverless functions, business rules |
160
+ | **Extensions & Rules** | 14 | Webhooks, serverless functions, business rules |
141
161
  | **Workspace Management** | 4 | Organize and delete workspaces |
142
162
  | **User Management** | 3 | List users and roles |
143
163
  | **Relations** | 4 | Annotation and document relations |
@@ -160,7 +180,7 @@ The server provides **56 tools** organized into categories:
160
180
  `get_engine`, `list_engines`, `create_engine`, `update_engine`, `create_engine_field`, `get_engine_fields`
161
181
 
162
182
  **Extensions & Rules:**
163
- `get_hook`, `list_hooks`, `create_hook`, `update_hook`, `list_hook_templates`, `create_hook_from_template`, `list_hook_logs`, `delete_hook`, `get_rule`, `list_rules`, `delete_rule`
183
+ `get_hook`, `list_hooks`, `create_hook`, `update_hook`, `list_hook_templates`, `create_hook_from_template`, `list_hook_logs`, `delete_hook`, `get_rule`, `list_rules`, `create_rule`, `update_rule`, `patch_rule`, `delete_rule`
164
184
 
165
185
  **Workspace Management:**
166
186
  `get_workspace`, `list_workspaces`, `create_workspace`, `delete_workspace`
@@ -2,14 +2,14 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- **MCP server for AI-powered Rossum document processing. 56 tools for queues, schemas, hooks, engines, and more.**
5
+ **MCP server for AI-powered Rossum document processing. 59 tools for queues, schemas, hooks, engines, and more.**
6
6
 
7
7
  [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://stancld.github.io/rossum-agents/)
8
- [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue.svg)](https://www.python.org/downloads/)
8
+ [![Python](https://img.shields.io/pypi/pyversions/rossum-mcp.svg)](https://pypi.org/project/rossum-mcp/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
  [![PyPI - rossum-mcp](https://img.shields.io/pypi/v/rossum-mcp?label=rossum-mcp)](https://pypi.org/project/rossum-mcp/)
11
11
  [![Coverage](https://codecov.io/gh/stancld/rossum-agents/branch/master/graph/badge.svg?flag=rossum-mcp)](https://codecov.io/gh/stancld/rossum-agents)
12
- [![MCP Tools](https://img.shields.io/badge/MCP_Tools-56-blue.svg)](#available-tools)
12
+ [![MCP Tools](https://img.shields.io/badge/MCP_Tools-59-blue.svg)](#available-tools)
13
13
 
14
14
  [![Rossum API](https://img.shields.io/badge/Rossum-API-orange.svg)](https://github.com/rossumai/rossum-api)
15
15
  [![MCP](https://img.shields.io/badge/MCP-compatible-green.svg)](https://modelcontextprotocol.io/)
@@ -78,9 +78,29 @@ Configure Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_c
78
78
 
79
79
  Set `ROSSUM_MCP_MODE=read-only` to disable all CREATE, UPDATE, and UPLOAD operations. Only GET and LIST operations will be available.
80
80
 
81
+ ### Runtime Mode Switching
82
+
83
+ Two tools allow dynamic mode control:
84
+
85
+ | Tool | Description |
86
+ |------|-------------|
87
+ | `get_mcp_mode` | Returns current operation mode (`read-only` or `read-write`) |
88
+ | `set_mcp_mode` | Switches between modes at runtime |
89
+
90
+ **Use case:** Start in read-only mode for safe exploration, then switch to read-write when ready to make changes.
91
+
92
+ ```
93
+ User: What mode are we in?
94
+ Assistant: [calls get_mcp_mode] → "read-only"
95
+
96
+ User: I'm ready to update the schema now.
97
+ Assistant: [calls set_mcp_mode("read-write")] → Mode switched to read-write
98
+ [calls update_schema(...)]
99
+ ```
100
+
81
101
  ## Available Tools
82
102
 
83
- The server provides **56 tools** organized into categories:
103
+ The server provides **59 tools** organized into categories:
84
104
 
85
105
  | Category | Tools | Description |
86
106
  |----------|-------|-------------|
@@ -88,7 +108,7 @@ The server provides **56 tools** organized into categories:
88
108
  | **Queue Management** | 9 | Create, configure, delete, and list queues |
89
109
  | **Schema Management** | 8 | Define, modify, and delete field structures |
90
110
  | **Engine Management** | 6 | Configure extraction and splitting engines |
91
- | **Extensions & Rules** | 11 | Webhooks, serverless functions, business rules |
111
+ | **Extensions & Rules** | 14 | Webhooks, serverless functions, business rules |
92
112
  | **Workspace Management** | 4 | Organize and delete workspaces |
93
113
  | **User Management** | 3 | List users and roles |
94
114
  | **Relations** | 4 | Annotation and document relations |
@@ -111,7 +131,7 @@ The server provides **56 tools** organized into categories:
111
131
  `get_engine`, `list_engines`, `create_engine`, `update_engine`, `create_engine_field`, `get_engine_fields`
112
132
 
113
133
  **Extensions & Rules:**
114
- `get_hook`, `list_hooks`, `create_hook`, `update_hook`, `list_hook_templates`, `create_hook_from_template`, `list_hook_logs`, `delete_hook`, `get_rule`, `list_rules`, `delete_rule`
134
+ `get_hook`, `list_hooks`, `create_hook`, `update_hook`, `list_hook_templates`, `create_hook_from_template`, `list_hook_logs`, `delete_hook`, `get_rule`, `list_rules`, `create_rule`, `update_rule`, `patch_rule`, `delete_rule`
115
135
 
116
136
  **Workspace Management:**
117
137
  `get_workspace`, `list_workspaces`, `create_workspace`, `delete_workspace`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rossum-mcp"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  description = "MCP server for AI-powered Rossum orchestration: document workflows, debug pipelines automatically, and configure intelligent document processing through natural language."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.1.0"
@@ -29,6 +29,7 @@ from rossum_mcp.tools import (
29
29
  register_user_tools,
30
30
  register_workspace_tools,
31
31
  )
32
+ from rossum_mcp.tools.base import get_mcp_mode, set_mcp_mode
32
33
 
33
34
  setup_logging(app_name="rossum-mcp-server", log_level="DEBUG", use_console=False)
34
35
 
@@ -36,12 +37,8 @@ logger = logging.getLogger(__name__)
36
37
 
37
38
  BASE_URL = os.environ["ROSSUM_API_BASE_URL"].rstrip("/")
38
39
  API_TOKEN = os.environ["ROSSUM_API_TOKEN"]
39
- MODE = os.environ.get("ROSSUM_MCP_MODE", "read-write").lower()
40
40
 
41
- if MODE not in ("read-only", "read-write"):
42
- raise ValueError(f"Invalid ROSSUM_MCP_MODE: {MODE}. Must be 'read-only' or 'read-write'")
43
-
44
- logger.info(f"Rossum MCP Server starting in {MODE} mode")
41
+ logger.info(f"Rossum MCP Server starting in {get_mcp_mode()} mode")
45
42
 
46
43
  mcp = FastMCP("rossum-mcp-server")
47
44
  client = AsyncRossumAPIClient(base_url=BASE_URL, credentials=Token(token=API_TOKEN))
@@ -60,6 +57,22 @@ register_user_tools(mcp, client)
60
57
  register_workspace_tools(mcp, client)
61
58
 
62
59
 
60
+ @mcp.tool(description="Get the current MCP operation mode (read-only or read-write).")
61
+ async def get_mcp_mode_tool() -> dict:
62
+ return {"mode": get_mcp_mode()}
63
+
64
+
65
+ @mcp.tool(
66
+ description="Set the MCP operation mode. Use 'read-only' to disable write operations, 'read-write' to enable them."
67
+ )
68
+ async def set_mcp_mode_tool(mode: str) -> dict:
69
+ try:
70
+ set_mcp_mode(mode)
71
+ return {"message": f"MCP mode set to '{get_mcp_mode()}'"}
72
+ except ValueError as e:
73
+ return {"error": str(e)}
74
+
75
+
63
76
  def main() -> None:
64
77
  """Main entry point for console script."""
65
78
  mcp.run()
@@ -13,11 +13,30 @@ if TYPE_CHECKING:
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
  BASE_URL = os.environ.get("ROSSUM_API_BASE_URL", "").rstrip("/")
16
- MODE = os.environ.get("ROSSUM_MCP_MODE", "read-write").lower()
17
16
 
18
17
  # Marker used to indicate omitted fields in list responses
19
18
  TRUNCATED_MARKER = "<omitted>"
20
19
 
20
+ VALID_MODES = ("read-only", "read-write")
21
+
22
+ _mcp_mode = os.environ.get("ROSSUM_MCP_MODE", "read-write").lower()
23
+ if _mcp_mode not in VALID_MODES:
24
+ raise ValueError(f"Invalid ROSSUM_MCP_MODE: {_mcp_mode}. Must be one of: {VALID_MODES}")
25
+
26
+
27
+ def get_mcp_mode() -> str:
28
+ """Return the current MCP mode."""
29
+ return _mcp_mode
30
+
31
+
32
+ def set_mcp_mode(mode: str) -> None:
33
+ """Set the MCP mode (case-insensitive)."""
34
+ global _mcp_mode
35
+ normalized = mode.lower()
36
+ if normalized not in VALID_MODES:
37
+ raise ValueError(f"Invalid mode '{mode}'. Must be one of: {VALID_MODES}")
38
+ _mcp_mode = normalized
39
+
21
40
 
22
41
  def build_resource_url(resource_type: str, resource_id: int) -> str:
23
42
  """Build a full URL for a Rossum API resource."""
@@ -26,7 +45,7 @@ def build_resource_url(resource_type: str, resource_id: int) -> str:
26
45
 
27
46
  def is_read_write_mode() -> bool:
28
47
  """Check if server is in read-write mode."""
29
- return MODE == "read-write"
48
+ return _mcp_mode == "read-write"
30
49
 
31
50
 
32
51
  def truncate_dict_fields(data: dict[str, Any], fields: tuple[str, ...]) -> dict[str, Any]:
@@ -16,7 +16,7 @@ class ToolInfo:
16
16
 
17
17
  name: str
18
18
  description: str
19
- destructive: bool = False
19
+ read_only: bool = True
20
20
 
21
21
 
22
22
  @dataclass
@@ -36,13 +36,13 @@ TOOL_CATALOG: dict[str, ToolCategory] = {
36
36
  name="annotations",
37
37
  description="Document processing: upload, retrieve, update, and confirm annotations",
38
38
  tools=[
39
- ToolInfo("upload_document", "Upload document to queue"),
39
+ ToolInfo("upload_document", "Upload document to queue", read_only=False),
40
40
  ToolInfo("get_annotation", "Retrieve annotation with extracted data"),
41
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),
42
+ ToolInfo("start_annotation", "Start annotation (to_review -> reviewing)", read_only=False),
43
+ ToolInfo("bulk_update_annotation_fields", "Bulk update annotation fields", read_only=False),
44
+ ToolInfo("confirm_annotation", "Confirm annotation (-> confirmed)", read_only=False),
45
+ ToolInfo("delete_annotation", "Delete annotation (soft delete)", read_only=False),
46
46
  ],
47
47
  keywords=["annotation", "document", "upload", "extract", "confirm", "review"],
48
48
  ),
@@ -54,11 +54,11 @@ TOOL_CATALOG: dict[str, ToolCategory] = {
54
54
  ToolInfo("list_queues", "List all queues"),
55
55
  ToolInfo("get_queue_schema", "Get queue's schema"),
56
56
  ToolInfo("get_queue_engine", "Get queue's AI engine"),
57
- ToolInfo("create_queue", "Create a queue"),
58
- ToolInfo("update_queue", "Update queue settings"),
57
+ ToolInfo("create_queue", "Create a queue", read_only=False),
58
+ ToolInfo("update_queue", "Update queue settings", read_only=False),
59
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),
60
+ ToolInfo("create_queue_from_template", "Create queue from template", read_only=False),
61
+ ToolInfo("delete_queue", "Delete queue (24h delayed)", read_only=False),
62
62
  ],
63
63
  keywords=["queue", "inbox", "connector"],
64
64
  ),
@@ -68,12 +68,12 @@ TOOL_CATALOG: dict[str, ToolCategory] = {
68
68
  tools=[
69
69
  ToolInfo("get_schema", "Retrieve schema details"),
70
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"),
71
+ ToolInfo("update_schema", "Update schema", read_only=False),
72
+ ToolInfo("create_schema", "Create new schema", read_only=False),
73
+ ToolInfo("patch_schema", "Add/update/remove schema fields", read_only=False),
74
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),
75
+ ToolInfo("prune_schema_fields", "Bulk remove schema fields", read_only=False),
76
+ ToolInfo("delete_schema", "Delete schema", read_only=False),
77
77
  ],
78
78
  keywords=["schema", "field", "datapoint", "section", "multivalue", "tuple"],
79
79
  ),
@@ -83,9 +83,9 @@ TOOL_CATALOG: dict[str, ToolCategory] = {
83
83
  tools=[
84
84
  ToolInfo("get_engine", "Retrieve engine details"),
85
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"),
86
+ ToolInfo("update_engine", "Update engine settings", read_only=False),
87
+ ToolInfo("create_engine", "Create new engine", read_only=False),
88
+ ToolInfo("create_engine_field", "Create engine field mapping", read_only=False),
89
89
  ToolInfo("get_engine_fields", "List engine fields"),
90
90
  ],
91
91
  keywords=["engine", "ai", "extractor", "splitter", "training"],
@@ -96,12 +96,12 @@ TOOL_CATALOG: dict[str, ToolCategory] = {
96
96
  tools=[
97
97
  ToolInfo("get_hook", "Retrieve hook details with code"),
98
98
  ToolInfo("list_hooks", "List all hooks for a queue"),
99
- ToolInfo("create_hook", "Create new hook"),
100
- ToolInfo("update_hook", "Update hook configuration"),
99
+ ToolInfo("create_hook", "Create new hook", read_only=False),
100
+ ToolInfo("update_hook", "Update hook configuration", read_only=False),
101
101
  ToolInfo("list_hook_logs", "View hook execution logs"),
102
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),
103
+ ToolInfo("create_hook_from_template", "Create hook from template", read_only=False),
104
+ ToolInfo("delete_hook", "Delete hook", read_only=False),
105
105
  ],
106
106
  keywords=["hook", "extension", "webhook", "automation", "function", "serverless"],
107
107
  ),
@@ -111,7 +111,7 @@ TOOL_CATALOG: dict[str, ToolCategory] = {
111
111
  tools=[
112
112
  ToolInfo("get_email_template", "Retrieve email template"),
113
113
  ToolInfo("list_email_templates", "List email templates"),
114
- ToolInfo("create_email_template", "Create email template"),
114
+ ToolInfo("create_email_template", "Create email template", read_only=False),
115
115
  ],
116
116
  keywords=["email", "template", "notification", "rejection"],
117
117
  ),
@@ -139,7 +139,10 @@ TOOL_CATALOG: dict[str, ToolCategory] = {
139
139
  tools=[
140
140
  ToolInfo("get_rule", "Retrieve rule details"),
141
141
  ToolInfo("list_rules", "List validation rules"),
142
- ToolInfo("delete_rule", "Delete rule", destructive=True),
142
+ ToolInfo("create_rule", "Create validation rule", read_only=False),
143
+ ToolInfo("update_rule", "Full update rule (PUT)", read_only=False),
144
+ ToolInfo("patch_rule", "Partial update rule (PATCH)", read_only=False),
145
+ ToolInfo("delete_rule", "Delete rule", read_only=False),
143
146
  ],
144
147
  keywords=["rule", "validation", "constraint"],
145
148
  ),
@@ -159,8 +162,8 @@ TOOL_CATALOG: dict[str, ToolCategory] = {
159
162
  tools=[
160
163
  ToolInfo("get_workspace", "Retrieve workspace details"),
161
164
  ToolInfo("list_workspaces", "List all workspaces"),
162
- ToolInfo("create_workspace", "Create new workspace"),
163
- ToolInfo("delete_workspace", "Delete workspace", destructive=True),
165
+ ToolInfo("create_workspace", "Create new workspace", read_only=False),
166
+ ToolInfo("delete_workspace", "Delete workspace", read_only=False),
164
167
  ],
165
168
  keywords=["workspace", "organization"],
166
169
  ),
@@ -21,8 +21,8 @@ def register_discovery_tools(mcp: FastMCP) -> None:
21
21
  @mcp.tool(
22
22
  description="List all available tool categories with descriptions, tool names, and keywords. "
23
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)."
24
+ "tools from specific categories before using them. Tools with read_only=false are write "
25
+ "operations (create, update, delete)."
26
26
  )
27
27
  async def list_tool_categories() -> list[dict]:
28
28
  return [
@@ -0,0 +1,241 @@
1
+ """Rule tools for Rossum MCP Server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING, Literal, TypedDict
7
+
8
+ from rossum_api.domain_logic.resources import Resource
9
+ from rossum_api.models.rule import Rule
10
+
11
+ from rossum_mcp.tools.base import build_resource_url, delete_resource, is_read_write_mode
12
+
13
+ if TYPE_CHECKING:
14
+ from fastmcp import FastMCP
15
+ from rossum_api import AsyncRossumAPIClient
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ RuleActionType = Literal[
21
+ "show_message",
22
+ "add_automation_blocker",
23
+ "add_validation_source",
24
+ "change_queue",
25
+ "send_email",
26
+ "hide_field",
27
+ "show_field",
28
+ "show_hide_field",
29
+ "change_status",
30
+ "add_label",
31
+ "remove_label",
32
+ "custom",
33
+ ]
34
+
35
+
36
+ class RuleActionPayload(TypedDict, total=False):
37
+ """Payload for rule actions. Fields depend on action type."""
38
+
39
+ type: Literal["info", "warning", "error"] # for show_message
40
+ content: str # message content or template
41
+ schema_id: str # target field schema_id
42
+ queue_url: str # for change_queue
43
+ status: str # for change_status
44
+ label: str # for add_label/remove_label
45
+
46
+
47
+ class RuleAction(TypedDict):
48
+ """Rule action definition."""
49
+
50
+ id: str # unique action identifier
51
+ type: RuleActionType
52
+ event: Literal["validation"]
53
+ payload: RuleActionPayload
54
+
55
+
56
+ async def _get_rule(client: AsyncRossumAPIClient, rule_id: int) -> Rule:
57
+ logger.debug(f"Retrieving rule: rule_id={rule_id}")
58
+ rule: Rule = await client.retrieve_rule(rule_id)
59
+ return rule
60
+
61
+
62
+ async def _list_rules(
63
+ client: AsyncRossumAPIClient,
64
+ schema_id: int | None = None,
65
+ organization_id: int | None = None,
66
+ enabled: bool | None = None,
67
+ ) -> list[Rule]:
68
+ logger.debug(f"Listing rules: schema_id={schema_id}, organization_id={organization_id}, enabled={enabled}")
69
+ filters: dict = {}
70
+ if schema_id is not None:
71
+ filters["schema"] = schema_id
72
+ if organization_id is not None:
73
+ filters["organization"] = organization_id
74
+ if enabled is not None:
75
+ filters["enabled"] = enabled
76
+
77
+ rules_list: list[Rule] = [rule async for rule in client.list_rules(**filters)]
78
+ return rules_list
79
+
80
+
81
+ async def _create_rule(
82
+ client: AsyncRossumAPIClient,
83
+ name: str,
84
+ schema_id: int,
85
+ trigger_condition: str,
86
+ actions: list[RuleAction],
87
+ enabled: bool = True,
88
+ queue_ids: list[int] | None = None,
89
+ ) -> Rule | dict:
90
+ if not is_read_write_mode():
91
+ return {"error": "create_rule is not available in read-only mode"}
92
+
93
+ schema_url = build_resource_url("schemas", schema_id)
94
+ logger.info(f"Creating rule: name={name}, schema_id={schema_id}, enabled={enabled}")
95
+
96
+ rule_data: dict = {
97
+ "name": name,
98
+ "schema": schema_url,
99
+ "trigger_condition": trigger_condition,
100
+ "actions": actions,
101
+ "enabled": enabled,
102
+ }
103
+
104
+ if queue_ids is not None:
105
+ rule_data["queues"] = [build_resource_url("queues", qid) for qid in queue_ids]
106
+
107
+ logger.debug(f"Rule creation payload: {rule_data}")
108
+ rule: Rule = await client.create_new_rule(rule_data)
109
+ logger.info(f"Successfully created rule: id={rule.id}, name={rule.name}")
110
+ return rule
111
+
112
+
113
+ async def _update_rule(
114
+ client: AsyncRossumAPIClient,
115
+ rule_id: int,
116
+ name: str,
117
+ trigger_condition: str,
118
+ actions: list[RuleAction],
119
+ enabled: bool,
120
+ queue_ids: list[int] | None = None,
121
+ ) -> Rule | dict:
122
+ """Full update (PUT) - all fields required."""
123
+ if not is_read_write_mode():
124
+ return {"error": "update_rule is not available in read-only mode"}
125
+
126
+ logger.info(f"Updating rule: rule_id={rule_id}, name={name}")
127
+ existing_rule: Rule = await client.retrieve_rule(rule_id)
128
+
129
+ rule_data: dict = {
130
+ "name": name,
131
+ "schema": existing_rule.schema,
132
+ "trigger_condition": trigger_condition,
133
+ "actions": actions,
134
+ "enabled": enabled,
135
+ }
136
+
137
+ if queue_ids is not None:
138
+ rule_data["queues"] = [build_resource_url("queues", qid) for qid in queue_ids]
139
+
140
+ logger.debug(f"Rule update payload: {rule_data}")
141
+ await client._http_client.update(Resource.Rule, rule_id, rule_data)
142
+ updated_rule: Rule = await client.retrieve_rule(rule_id)
143
+ logger.info(f"Successfully updated rule: id={updated_rule.id}")
144
+ return updated_rule
145
+
146
+
147
+ async def _patch_rule(
148
+ client: AsyncRossumAPIClient,
149
+ rule_id: int,
150
+ name: str | None = None,
151
+ trigger_condition: str | None = None,
152
+ actions: list[RuleAction] | None = None,
153
+ enabled: bool | None = None,
154
+ queue_ids: list[int] | None = None,
155
+ ) -> Rule | dict:
156
+ """Partial update (PATCH) - only provided fields are updated."""
157
+ if not is_read_write_mode():
158
+ return {"error": "patch_rule is not available in read-only mode"}
159
+
160
+ logger.info(f"Patching rule: rule_id={rule_id}")
161
+
162
+ patch_data: dict = {}
163
+ if name is not None:
164
+ patch_data["name"] = name
165
+ if trigger_condition is not None:
166
+ patch_data["trigger_condition"] = trigger_condition
167
+ if actions is not None:
168
+ patch_data["actions"] = actions
169
+ if enabled is not None:
170
+ patch_data["enabled"] = enabled
171
+ if queue_ids is not None:
172
+ patch_data["queues"] = [build_resource_url("queues", qid) for qid in queue_ids]
173
+
174
+ if not patch_data:
175
+ return {"error": "No fields provided to update"}
176
+
177
+ logger.debug(f"Rule patch payload: {patch_data}")
178
+ updated_rule: Rule = await client.update_part_rule(rule_id, patch_data)
179
+ logger.info(f"Successfully patched rule: id={updated_rule.id}")
180
+ return updated_rule
181
+
182
+
183
+ async def _delete_rule(client: AsyncRossumAPIClient, rule_id: int) -> dict:
184
+ return await delete_resource("rule", rule_id, client.delete_rule)
185
+
186
+
187
+ def register_rule_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
188
+ """Register rule-related tools with the FastMCP server."""
189
+
190
+ @mcp.tool(description="Retrieve rule details.")
191
+ async def get_rule(rule_id: int) -> Rule:
192
+ return await _get_rule(client, rule_id)
193
+
194
+ @mcp.tool(description="List all rules.")
195
+ async def list_rules(
196
+ schema_id: int | None = None, organization_id: int | None = None, enabled: bool | None = None
197
+ ) -> list[Rule]:
198
+ return await _list_rules(client, schema_id, organization_id, enabled)
199
+
200
+ @mcp.tool(
201
+ description="Create a new rule. Rules automate field operations based on trigger conditions (TxScript formulas like 'field.amount > 1000'). Actions require: id (unique str), type (show_message|hide_field|show_field|change_status|custom|etc), event (validation), payload (dict with type, content, schema_id for show_message). queue_ids limits rule to specific queues."
202
+ )
203
+ async def create_rule(
204
+ name: str,
205
+ schema_id: int,
206
+ trigger_condition: str,
207
+ actions: list[RuleAction],
208
+ enabled: bool = True,
209
+ queue_ids: list[int] | None = None,
210
+ ) -> Rule | dict:
211
+ return await _create_rule(client, name, schema_id, trigger_condition, actions, enabled, queue_ids)
212
+
213
+ @mcp.tool(
214
+ description="Full update of a rule (PUT). All fields required. Use patch_rule for partial updates. queue_ids limits rule to specific queues."
215
+ )
216
+ async def update_rule(
217
+ rule_id: int,
218
+ name: str,
219
+ trigger_condition: str,
220
+ actions: list[RuleAction],
221
+ enabled: bool,
222
+ queue_ids: list[int] | None = None,
223
+ ) -> Rule | dict:
224
+ return await _update_rule(client, rule_id, name, trigger_condition, actions, enabled, queue_ids)
225
+
226
+ @mcp.tool(
227
+ description="Partial update of a rule (PATCH). Only provided fields are updated. queue_ids limits rule to specific queues (empty list removes all)."
228
+ )
229
+ async def patch_rule(
230
+ rule_id: int,
231
+ name: str | None = None,
232
+ trigger_condition: str | None = None,
233
+ actions: list[RuleAction] | None = None,
234
+ enabled: bool | None = None,
235
+ queue_ids: list[int] | None = None,
236
+ ) -> Rule | dict:
237
+ return await _patch_rule(client, rule_id, name, trigger_condition, actions, enabled, queue_ids)
238
+
239
+ @mcp.tool(description="Delete a rule.")
240
+ async def delete_rule(rule_id: int) -> dict:
241
+ return await _delete_rule(client, rule_id)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rossum-mcp
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: MCP server for AI-powered Rossum orchestration: document workflows, debug pipelines automatically, and configure intelligent document processing through natural language.
5
5
  Author-email: "Dan Stancl (Rossum AI)" <daniel.stancl@gmail.com>
6
6
  License: MIT
@@ -51,14 +51,14 @@ Dynamic: license-file
51
51
 
52
52
  <div align="center">
53
53
 
54
- **MCP server for AI-powered Rossum document processing. 56 tools for queues, schemas, hooks, engines, and more.**
54
+ **MCP server for AI-powered Rossum document processing. 59 tools for queues, schemas, hooks, engines, and more.**
55
55
 
56
56
  [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://stancld.github.io/rossum-agents/)
57
- [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue.svg)](https://www.python.org/downloads/)
57
+ [![Python](https://img.shields.io/pypi/pyversions/rossum-mcp.svg)](https://pypi.org/project/rossum-mcp/)
58
58
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
59
  [![PyPI - rossum-mcp](https://img.shields.io/pypi/v/rossum-mcp?label=rossum-mcp)](https://pypi.org/project/rossum-mcp/)
60
60
  [![Coverage](https://codecov.io/gh/stancld/rossum-agents/branch/master/graph/badge.svg?flag=rossum-mcp)](https://codecov.io/gh/stancld/rossum-agents)
61
- [![MCP Tools](https://img.shields.io/badge/MCP_Tools-56-blue.svg)](#available-tools)
61
+ [![MCP Tools](https://img.shields.io/badge/MCP_Tools-59-blue.svg)](#available-tools)
62
62
 
63
63
  [![Rossum API](https://img.shields.io/badge/Rossum-API-orange.svg)](https://github.com/rossumai/rossum-api)
64
64
  [![MCP](https://img.shields.io/badge/MCP-compatible-green.svg)](https://modelcontextprotocol.io/)
@@ -127,9 +127,29 @@ Configure Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_c
127
127
 
128
128
  Set `ROSSUM_MCP_MODE=read-only` to disable all CREATE, UPDATE, and UPLOAD operations. Only GET and LIST operations will be available.
129
129
 
130
+ ### Runtime Mode Switching
131
+
132
+ Two tools allow dynamic mode control:
133
+
134
+ | Tool | Description |
135
+ |------|-------------|
136
+ | `get_mcp_mode` | Returns current operation mode (`read-only` or `read-write`) |
137
+ | `set_mcp_mode` | Switches between modes at runtime |
138
+
139
+ **Use case:** Start in read-only mode for safe exploration, then switch to read-write when ready to make changes.
140
+
141
+ ```
142
+ User: What mode are we in?
143
+ Assistant: [calls get_mcp_mode] → "read-only"
144
+
145
+ User: I'm ready to update the schema now.
146
+ Assistant: [calls set_mcp_mode("read-write")] → Mode switched to read-write
147
+ [calls update_schema(...)]
148
+ ```
149
+
130
150
  ## Available Tools
131
151
 
132
- The server provides **56 tools** organized into categories:
152
+ The server provides **59 tools** organized into categories:
133
153
 
134
154
  | Category | Tools | Description |
135
155
  |----------|-------|-------------|
@@ -137,7 +157,7 @@ The server provides **56 tools** organized into categories:
137
157
  | **Queue Management** | 9 | Create, configure, delete, and list queues |
138
158
  | **Schema Management** | 8 | Define, modify, and delete field structures |
139
159
  | **Engine Management** | 6 | Configure extraction and splitting engines |
140
- | **Extensions & Rules** | 11 | Webhooks, serverless functions, business rules |
160
+ | **Extensions & Rules** | 14 | Webhooks, serverless functions, business rules |
141
161
  | **Workspace Management** | 4 | Organize and delete workspaces |
142
162
  | **User Management** | 3 | List users and roles |
143
163
  | **Relations** | 4 | Annotation and document relations |
@@ -160,7 +180,7 @@ The server provides **56 tools** organized into categories:
160
180
  `get_engine`, `list_engines`, `create_engine`, `update_engine`, `create_engine_field`, `get_engine_fields`
161
181
 
162
182
  **Extensions & Rules:**
163
- `get_hook`, `list_hooks`, `create_hook`, `update_hook`, `list_hook_templates`, `create_hook_from_template`, `list_hook_logs`, `delete_hook`, `get_rule`, `list_rules`, `delete_rule`
183
+ `get_hook`, `list_hooks`, `create_hook`, `update_hook`, `list_hook_templates`, `create_hook_from_template`, `list_hook_logs`, `delete_hook`, `get_rule`, `list_rules`, `create_rule`, `update_rule`, `patch_rule`, `delete_rule`
164
184
 
165
185
  **Workspace Management:**
166
186
  `get_workspace`, `list_workspaces`, `create_workspace`, `delete_workspace`
@@ -69,19 +69,41 @@ class TestToolCatalog:
69
69
  }
70
70
  assert tool_names == expected
71
71
 
72
- def test_destructive_tools_are_marked(self) -> None:
73
- destructive_tools = [
74
- (cat_name, t.name) for cat_name, cat in TOOL_CATALOG.items() for t in cat.tools if t.destructive
72
+ def test_write_tools_are_marked(self) -> None:
73
+ write_tools = [
74
+ (cat_name, t.name) for cat_name, cat in TOOL_CATALOG.items() for t in cat.tools if not t.read_only
75
75
  ]
76
- expected_destructive = {
76
+ expected_write = {
77
+ ("annotations", "upload_document"),
78
+ ("annotations", "start_annotation"),
79
+ ("annotations", "bulk_update_annotation_fields"),
80
+ ("annotations", "confirm_annotation"),
77
81
  ("annotations", "delete_annotation"),
82
+ ("queues", "create_queue"),
83
+ ("queues", "update_queue"),
84
+ ("queues", "create_queue_from_template"),
78
85
  ("queues", "delete_queue"),
86
+ ("schemas", "update_schema"),
87
+ ("schemas", "create_schema"),
88
+ ("schemas", "patch_schema"),
89
+ ("schemas", "prune_schema_fields"),
79
90
  ("schemas", "delete_schema"),
91
+ ("engines", "update_engine"),
92
+ ("engines", "create_engine"),
93
+ ("engines", "create_engine_field"),
94
+ ("hooks", "create_hook"),
95
+ ("hooks", "update_hook"),
96
+ ("hooks", "create_hook_from_template"),
80
97
  ("hooks", "delete_hook"),
98
+ ("email_templates", "create_email_template"),
99
+ ("rules", "create_rule"),
100
+ ("rules", "update_rule"),
101
+ ("rules", "patch_rule"),
81
102
  ("rules", "delete_rule"),
103
+ ("workspaces", "create_workspace"),
82
104
  ("workspaces", "delete_workspace"),
83
105
  }
84
- assert set(destructive_tools) == expected_destructive
106
+ assert set(write_tools) == expected_write
85
107
 
86
108
 
87
109
  class TestCatalogSummary:
@@ -1,63 +0,0 @@
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
9
-
10
- from rossum_mcp.tools.base import delete_resource
11
-
12
- if TYPE_CHECKING:
13
- from fastmcp import FastMCP
14
- from rossum_api import AsyncRossumAPIClient
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- async def _get_rule(client: AsyncRossumAPIClient, rule_id: int) -> Rule:
20
- logger.debug(f"Retrieving rule: rule_id={rule_id}")
21
- rule: Rule = await client.retrieve_rule(rule_id)
22
- return rule
23
-
24
-
25
- async def _list_rules(
26
- client: AsyncRossumAPIClient,
27
- schema_id: int | None = None,
28
- organization_id: int | None = None,
29
- enabled: bool | None = None,
30
- ) -> list[Rule]:
31
- logger.debug(f"Listing rules: schema_id={schema_id}, organization_id={organization_id}, enabled={enabled}")
32
- filters: dict = {}
33
- if schema_id is not None:
34
- filters["schema"] = schema_id
35
- if organization_id is not None:
36
- filters["organization"] = organization_id
37
- if enabled is not None:
38
- filters["enabled"] = enabled
39
-
40
- rules_list: list[Rule] = [rule async for rule in client.list_rules(**filters)]
41
- return rules_list
42
-
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
-
48
- def register_rule_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
49
- """Register rule-related tools with the FastMCP server."""
50
-
51
- @mcp.tool(description="Retrieve rule details.")
52
- async def get_rule(rule_id: int) -> Rule:
53
- return await _get_rule(client, rule_id)
54
-
55
- @mcp.tool(description="List all rules.")
56
- async def list_rules(
57
- schema_id: int | None = None, organization_id: int | None = None, enabled: bool | None = None
58
- ) -> list[Rule]:
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)
File without changes
File without changes