rossum-mcp 0.3.4__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rossum_mcp/__init__.py +1 -1
- rossum_mcp/server.py +4 -0
- rossum_mcp/tools/__init__.py +14 -0
- rossum_mcp/tools/annotations.py +123 -86
- rossum_mcp/tools/base.py +22 -0
- rossum_mcp/tools/catalog.py +169 -0
- rossum_mcp/tools/discovery.py +36 -0
- rossum_mcp/tools/document_relations.py +1 -3
- rossum_mcp/tools/email_templates.py +131 -0
- rossum_mcp/tools/engines.py +106 -77
- rossum_mcp/tools/hooks.py +228 -150
- rossum_mcp/tools/queues.py +244 -85
- rossum_mcp/tools/relations.py +3 -7
- rossum_mcp/tools/rules.py +28 -17
- rossum_mcp/tools/schemas.py +505 -105
- rossum_mcp/tools/users.py +51 -27
- rossum_mcp/tools/workspaces.py +47 -37
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.4.0.dist-info}/METADATA +370 -12
- rossum_mcp-0.4.0.dist-info/RECORD +24 -0
- rossum_mcp-0.3.4.dist-info/RECORD +0 -21
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.4.0.dist-info}/WHEEL +0 -0
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.4.0.dist-info}/entry_points.txt +0 -0
- {rossum_mcp-0.3.4.dist-info → rossum_mcp-0.4.0.dist-info}/top_level.txt +0 -0
rossum_mcp/__init__.py
CHANGED
rossum_mcp/server.py
CHANGED
|
@@ -17,7 +17,9 @@ 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,
|
|
22
|
+
register_email_template_tools,
|
|
21
23
|
register_engine_tools,
|
|
22
24
|
register_hook_tools,
|
|
23
25
|
register_queue_tools,
|
|
@@ -44,11 +46,13 @@ logger.info(f"Rossum MCP Server starting in {MODE} mode")
|
|
|
44
46
|
mcp = FastMCP("rossum-mcp-server")
|
|
45
47
|
client = AsyncRossumAPIClient(base_url=BASE_URL, credentials=Token(token=API_TOKEN))
|
|
46
48
|
|
|
49
|
+
register_discovery_tools(mcp)
|
|
47
50
|
register_annotation_tools(mcp, client)
|
|
48
51
|
register_queue_tools(mcp, client)
|
|
49
52
|
register_schema_tools(mcp, client)
|
|
50
53
|
register_engine_tools(mcp, client)
|
|
51
54
|
register_hook_tools(mcp, client)
|
|
55
|
+
register_email_template_tools(mcp, client)
|
|
52
56
|
register_document_relation_tools(mcp, client)
|
|
53
57
|
register_relation_tools(mcp, client)
|
|
54
58
|
register_rule_tools(mcp, client)
|
rossum_mcp/tools/__init__.py
CHANGED
|
@@ -3,7 +3,15 @@
|
|
|
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
|
|
14
|
+
from rossum_mcp.tools.email_templates import register_email_template_tools
|
|
7
15
|
from rossum_mcp.tools.engines import register_engine_tools
|
|
8
16
|
from rossum_mcp.tools.hooks import register_hook_tools
|
|
9
17
|
from rossum_mcp.tools.queues import register_queue_tools
|
|
@@ -14,8 +22,14 @@ from rossum_mcp.tools.users import register_user_tools
|
|
|
14
22
|
from rossum_mcp.tools.workspaces import register_workspace_tools
|
|
15
23
|
|
|
16
24
|
__all__ = [
|
|
25
|
+
"TOOL_CATALOG",
|
|
26
|
+
"ToolCategory",
|
|
27
|
+
"ToolInfo",
|
|
28
|
+
"get_catalog_summary",
|
|
17
29
|
"register_annotation_tools",
|
|
30
|
+
"register_discovery_tools",
|
|
18
31
|
"register_document_relation_tools",
|
|
32
|
+
"register_email_template_tools",
|
|
19
33
|
"register_engine_tools",
|
|
20
34
|
"register_hook_tools",
|
|
21
35
|
"register_queue_tools",
|
rossum_mcp/tools/annotations.py
CHANGED
|
@@ -7,7 +7,7 @@ from collections.abc import Sequence # noqa: TC003 - needed at runtime for Fast
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TYPE_CHECKING, Literal
|
|
9
9
|
|
|
10
|
-
from rossum_api.models.annotation import Annotation
|
|
10
|
+
from rossum_api.models.annotation import Annotation
|
|
11
11
|
|
|
12
12
|
from rossum_mcp.tools.base import is_read_write_mode
|
|
13
13
|
|
|
@@ -17,114 +17,151 @@ if TYPE_CHECKING:
|
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
|
-
# Fixed sideloads (critical for well-behaving agent)
|
|
21
20
|
type Sideload = Literal["content", "document", "automation_blocker"]
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
def
|
|
23
|
+
async def _upload_document(client: AsyncRossumAPIClient, file_path: str, queue_id: int) -> dict:
|
|
24
|
+
if not is_read_write_mode():
|
|
25
|
+
return {"error": "upload_document is not available in read-only mode"}
|
|
26
|
+
|
|
27
|
+
path = Path(file_path)
|
|
28
|
+
if not path.exists():
|
|
29
|
+
logger.error(f"File not found: {file_path}")
|
|
30
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
task = (await client.upload_document(queue_id, [(str(path), path.name)]))[0]
|
|
34
|
+
except KeyError as e:
|
|
35
|
+
logger.error(f"Upload failed - unexpected API response format: {e!s}")
|
|
36
|
+
error_msg = (
|
|
37
|
+
f"Document upload failed - API response missing expected key {e!s}. "
|
|
38
|
+
f"This usually means either:\n"
|
|
39
|
+
f"1. The queue_id ({queue_id}) is invalid or you don't have access to it\n"
|
|
40
|
+
f"2. The Rossum API returned an error response\n"
|
|
41
|
+
f"Please verify:\n"
|
|
42
|
+
f"- The queue_id is correct and exists in your workspace\n"
|
|
43
|
+
f"- You have permission to upload documents to this queue\n"
|
|
44
|
+
f"- Your API token has the necessary permissions"
|
|
45
|
+
)
|
|
46
|
+
raise ValueError(error_msg) from e
|
|
47
|
+
except IndexError as e:
|
|
48
|
+
logger.error(f"Upload failed - no tasks returned: {e}")
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f"Document upload failed - no tasks were created. This may indicate the queue_id ({queue_id}) is invalid."
|
|
51
|
+
) from e
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.error(f"Upload failed: {type(e).__name__}: {e}")
|
|
54
|
+
raise ValueError(f"Document upload failed: {type(e).__name__}: {e!s}") from e
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"task_id": task.id,
|
|
58
|
+
"task_status": task.status,
|
|
59
|
+
"queue_id": queue_id,
|
|
60
|
+
"message": "Document upload initiated. Use `list_annotations` to find the annotation ID for this queue.",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def _get_annotation(
|
|
65
|
+
client: AsyncRossumAPIClient, annotation_id: int, sideloads: Sequence[Sideload] = ()
|
|
66
|
+
) -> Annotation:
|
|
67
|
+
logger.debug(f"Retrieving annotation: annotation_id={annotation_id}")
|
|
68
|
+
annotation: Annotation = await client.retrieve_annotation(annotation_id, sideloads) # type: ignore[arg-type]
|
|
69
|
+
return annotation
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def _list_annotations(
|
|
73
|
+
client: AsyncRossumAPIClient,
|
|
74
|
+
queue_id: int,
|
|
75
|
+
status: str | None = "importing,to_review,confirmed,exported",
|
|
76
|
+
ordering: Sequence[str] = (),
|
|
77
|
+
first_n: int | None = None,
|
|
78
|
+
) -> list[Annotation]:
|
|
79
|
+
logger.debug(f"Listing annotations: queue_id={queue_id}, status={status}, ordering={ordering}, first_n={first_n}")
|
|
80
|
+
params: dict = {"queue": queue_id, "page_size": 100}
|
|
81
|
+
if status:
|
|
82
|
+
params["status"] = status
|
|
83
|
+
if ordering:
|
|
84
|
+
params["ordering"] = ordering
|
|
85
|
+
|
|
86
|
+
annotations_list: list[Annotation] = []
|
|
87
|
+
async for item in client.list_annotations(**params):
|
|
88
|
+
annotations_list.append(item)
|
|
89
|
+
if first_n is not None and len(annotations_list) >= first_n:
|
|
90
|
+
break
|
|
91
|
+
return annotations_list
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def _start_annotation(client: AsyncRossumAPIClient, annotation_id: int) -> dict:
|
|
95
|
+
if not is_read_write_mode():
|
|
96
|
+
return {"error": "start_annotation is not available in read-only mode"}
|
|
97
|
+
|
|
98
|
+
logger.debug(f"Starting annotation: annotation_id={annotation_id}")
|
|
99
|
+
await client.start_annotation(annotation_id)
|
|
100
|
+
return {
|
|
101
|
+
"annotation_id": annotation_id,
|
|
102
|
+
"message": f"Annotation {annotation_id} started successfully. Status changed to 'reviewing'.",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def _bulk_update_annotation_fields(
|
|
107
|
+
client: AsyncRossumAPIClient, annotation_id: int, operations: list[dict]
|
|
108
|
+
) -> dict:
|
|
109
|
+
if not is_read_write_mode():
|
|
110
|
+
return {"error": "bulk_update_annotation_fields is not available in read-only mode"}
|
|
111
|
+
|
|
112
|
+
logger.debug(f"Bulk updating annotation: annotation_id={annotation_id}, ops={operations}")
|
|
113
|
+
await client.bulk_update_annotation_data(annotation_id, operations)
|
|
114
|
+
return {
|
|
115
|
+
"annotation_id": annotation_id,
|
|
116
|
+
"operations_count": len(operations),
|
|
117
|
+
"message": f"Annotation {annotation_id} updated with {len(operations)} operations successfully.",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def _confirm_annotation(client: AsyncRossumAPIClient, annotation_id: int) -> dict:
|
|
122
|
+
if not is_read_write_mode():
|
|
123
|
+
return {"error": "confirm_annotation is not available in read-only mode"}
|
|
124
|
+
|
|
125
|
+
logger.debug(f"Confirming annotation: annotation_id={annotation_id}")
|
|
126
|
+
await client.confirm_annotation(annotation_id)
|
|
127
|
+
return {
|
|
128
|
+
"annotation_id": annotation_id,
|
|
129
|
+
"message": f"Annotation {annotation_id} confirmed successfully. Status changed to 'confirmed'.",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def register_annotation_tools(mcp: FastMCP, client: AsyncRossumAPIClient) -> None:
|
|
25
134
|
"""Register annotation-related tools with the FastMCP server."""
|
|
26
135
|
|
|
27
136
|
@mcp.tool(description="Upload a document to Rossum. Use list_annotations to get annotation ID.")
|
|
28
137
|
async def upload_document(file_path: str, queue_id: int) -> dict:
|
|
29
|
-
|
|
30
|
-
if not is_read_write_mode():
|
|
31
|
-
return {"error": "upload_document is not available in read-only mode"}
|
|
32
|
-
|
|
33
|
-
path = Path(file_path)
|
|
34
|
-
if not path.exists():
|
|
35
|
-
logger.error(f"File not found: {file_path}")
|
|
36
|
-
raise FileNotFoundError(f"File not found: {file_path}")
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
task = (await client.upload_document(queue_id, [(str(path), path.name)]))[0]
|
|
40
|
-
except KeyError as e:
|
|
41
|
-
logger.error(f"Upload failed - unexpected API response format: {e!s}")
|
|
42
|
-
error_msg = (
|
|
43
|
-
f"Document upload failed - API response missing expected key {e!s}. "
|
|
44
|
-
f"This usually means either:\n"
|
|
45
|
-
f"1. The queue_id ({queue_id}) is invalid or you don't have access to it\n"
|
|
46
|
-
f"2. The Rossum API returned an error response\n"
|
|
47
|
-
f"Please verify:\n"
|
|
48
|
-
f"- The queue_id is correct and exists in your workspace\n"
|
|
49
|
-
f"- You have permission to upload documents to this queue\n"
|
|
50
|
-
f"- Your API token has the necessary permissions"
|
|
51
|
-
)
|
|
52
|
-
raise ValueError(error_msg) from e
|
|
53
|
-
except IndexError as e:
|
|
54
|
-
logger.error(f"Upload failed - no tasks returned: {e}")
|
|
55
|
-
raise ValueError(
|
|
56
|
-
f"Document upload failed - no tasks were created. This may indicate the queue_id ({queue_id}) is invalid."
|
|
57
|
-
) from e
|
|
58
|
-
except Exception as e:
|
|
59
|
-
logger.error(f"Upload failed: {type(e).__name__}: {e}")
|
|
60
|
-
raise ValueError(f"Document upload failed: {type(e).__name__}: {e!s}") from e
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
"task_id": task.id,
|
|
64
|
-
"task_status": task.status,
|
|
65
|
-
"queue_id": queue_id,
|
|
66
|
-
"message": "Document upload initiated. Use `list_annotations` to find the annotation ID for this queue.",
|
|
67
|
-
}
|
|
138
|
+
return await _upload_document(client, file_path, queue_id)
|
|
68
139
|
|
|
69
140
|
@mcp.tool(description="Retrieve annotation data. Use 'content' sideload to get extracted data.")
|
|
70
141
|
async def get_annotation(annotation_id: int, sideloads: Sequence[Sideload] = ()) -> Annotation:
|
|
71
|
-
|
|
72
|
-
logger.debug(f"Retrieving annotation: annotation_id={annotation_id}")
|
|
73
|
-
annotation: Annotation = await client.retrieve_annotation(annotation_id, sideloads) # type: ignore[arg-type]
|
|
74
|
-
return annotation
|
|
142
|
+
return await _get_annotation(client, annotation_id, sideloads)
|
|
75
143
|
|
|
76
|
-
@mcp.tool(description="List annotations for a queue.")
|
|
144
|
+
@mcp.tool(description="List annotations for a queue. Use ordering=['-created_at'] to sort by newest first.")
|
|
77
145
|
async def list_annotations(
|
|
78
|
-
queue_id: int,
|
|
146
|
+
queue_id: int,
|
|
147
|
+
status: str | None = "importing,to_review,confirmed,exported",
|
|
148
|
+
ordering: Sequence[str] = (),
|
|
149
|
+
first_n: int | None = None,
|
|
79
150
|
) -> list[Annotation]:
|
|
80
|
-
|
|
81
|
-
logger.debug(f"Listing annotations: queue_id={queue_id}, status={status}")
|
|
82
|
-
params: dict = {"queue": queue_id, "page_size": 100}
|
|
83
|
-
if status:
|
|
84
|
-
params["status"] = status
|
|
85
|
-
annotations_list: list[Annotation] = [item async for item in client.list_annotations(**params)]
|
|
86
|
-
return annotations_list
|
|
151
|
+
return await _list_annotations(client, queue_id, status, ordering, first_n)
|
|
87
152
|
|
|
88
153
|
@mcp.tool(description="Start annotation (move from 'to_review' to 'reviewing').")
|
|
89
154
|
async def start_annotation(annotation_id: int) -> dict:
|
|
90
|
-
|
|
91
|
-
if not is_read_write_mode():
|
|
92
|
-
return {"error": "start_annotation is not available in read-only mode"}
|
|
93
|
-
|
|
94
|
-
logger.debug(f"Starting annotation: annotation_id={annotation_id}")
|
|
95
|
-
await client.start_annotation(annotation_id)
|
|
96
|
-
return {
|
|
97
|
-
"annotation_id": annotation_id,
|
|
98
|
-
"message": f"Annotation {annotation_id} started successfully. Status changed to 'reviewing'.",
|
|
99
|
-
}
|
|
155
|
+
return await _start_annotation(client, annotation_id)
|
|
100
156
|
|
|
101
157
|
@mcp.tool(
|
|
102
158
|
description="Bulk update annotation fields. It can be used after `start_annotation` only. Use datapoint ID from content, NOT schema_id."
|
|
103
159
|
)
|
|
104
160
|
async def bulk_update_annotation_fields(annotation_id: int, operations: list[dict]) -> dict:
|
|
105
|
-
|
|
106
|
-
if not is_read_write_mode():
|
|
107
|
-
return {"error": "bulk_update_annotation_fields is not available in read-only mode"}
|
|
108
|
-
|
|
109
|
-
logger.debug(f"Bulk updating annotation: annotation_id={annotation_id}, ops={operations}")
|
|
110
|
-
await client.bulk_update_annotation_data(annotation_id, operations)
|
|
111
|
-
return {
|
|
112
|
-
"annotation_id": annotation_id,
|
|
113
|
-
"operations_count": len(operations),
|
|
114
|
-
"message": f"Annotation {annotation_id} updated with {len(operations)} operations successfully.",
|
|
115
|
-
}
|
|
161
|
+
return await _bulk_update_annotation_fields(client, annotation_id, operations)
|
|
116
162
|
|
|
117
163
|
@mcp.tool(
|
|
118
164
|
description="Confirm annotation (move to 'confirmed'). It can be used after `bulk_update_annotation_fields`."
|
|
119
165
|
)
|
|
120
166
|
async def confirm_annotation(annotation_id: int) -> dict:
|
|
121
|
-
|
|
122
|
-
if not is_read_write_mode():
|
|
123
|
-
return {"error": "confirm_annotation is not available in read-only mode"}
|
|
124
|
-
|
|
125
|
-
logger.debug(f"Confirming annotation: annotation_id={annotation_id}")
|
|
126
|
-
await client.confirm_annotation(annotation_id)
|
|
127
|
-
return {
|
|
128
|
-
"annotation_id": annotation_id,
|
|
129
|
-
"message": f"Annotation {annotation_id} confirmed successfully. Status changed to 'confirmed'.",
|
|
130
|
-
}
|
|
167
|
+
return await _confirm_annotation(client, annotation_id)
|
rossum_mcp/tools/base.py
CHANGED
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from typing import Any
|
|
6
10
|
|
|
7
11
|
BASE_URL = os.environ.get("ROSSUM_API_BASE_URL", "").rstrip("/")
|
|
8
12
|
MODE = os.environ.get("ROSSUM_MCP_MODE", "read-write").lower()
|
|
9
13
|
|
|
14
|
+
# Marker used to indicate omitted fields in list responses
|
|
15
|
+
TRUNCATED_MARKER = "<omitted>"
|
|
16
|
+
|
|
10
17
|
|
|
11
18
|
def build_resource_url(resource_type: str, resource_id: int) -> str:
|
|
12
19
|
"""Build a full URL for a Rossum API resource."""
|
|
@@ -16,3 +23,18 @@ def build_resource_url(resource_type: str, resource_id: int) -> str:
|
|
|
16
23
|
def is_read_write_mode() -> bool:
|
|
17
24
|
"""Check if server is in read-write mode."""
|
|
18
25
|
return MODE == "read-write"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def truncate_dict_fields(data: dict[str, Any], fields: tuple[str, ...]) -> dict[str, Any]:
|
|
29
|
+
"""Truncate specified fields in a dictionary to save context.
|
|
30
|
+
|
|
31
|
+
Returns a new dictionary with specified fields replaced by TRUNCATED_MARKER.
|
|
32
|
+
"""
|
|
33
|
+
if not data:
|
|
34
|
+
return data
|
|
35
|
+
|
|
36
|
+
result = dict(data)
|
|
37
|
+
for field in fields:
|
|
38
|
+
if field in result:
|
|
39
|
+
result[field] = TRUNCATED_MARKER
|
|
40
|
+
return result
|
|
@@ -0,0 +1,169 @@
|
|
|
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
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ToolCategory:
|
|
23
|
+
"""A category of related tools."""
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
description: str
|
|
27
|
+
tools: list[ToolInfo]
|
|
28
|
+
keywords: list[str]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Tool catalog organized by functional category
|
|
32
|
+
# Keywords enable automatic pre-loading based on user request text
|
|
33
|
+
TOOL_CATALOG: dict[str, ToolCategory] = {
|
|
34
|
+
"annotations": ToolCategory(
|
|
35
|
+
name="annotations",
|
|
36
|
+
description="Document processing: upload, retrieve, update, and confirm annotations",
|
|
37
|
+
tools=[
|
|
38
|
+
ToolInfo("upload_document", "Upload document to queue"),
|
|
39
|
+
ToolInfo("get_annotation", "Retrieve annotation with extracted data"),
|
|
40
|
+
ToolInfo("list_annotations", "List annotations for a queue"),
|
|
41
|
+
ToolInfo("start_annotation", "Start annotation (to_review -> reviewing)"),
|
|
42
|
+
ToolInfo("bulk_update_annotation_fields", "Bulk update annotation fields"),
|
|
43
|
+
ToolInfo("confirm_annotation", "Confirm annotation (-> confirmed)"),
|
|
44
|
+
],
|
|
45
|
+
keywords=["annotation", "document", "upload", "extract", "confirm", "review"],
|
|
46
|
+
),
|
|
47
|
+
"queues": ToolCategory(
|
|
48
|
+
name="queues",
|
|
49
|
+
description="Queue management: create, configure, and list document processing queues",
|
|
50
|
+
tools=[
|
|
51
|
+
ToolInfo("get_queue", "Retrieve queue details"),
|
|
52
|
+
ToolInfo("list_queues", "List all queues"),
|
|
53
|
+
ToolInfo("get_queue_schema", "Get queue's schema"),
|
|
54
|
+
ToolInfo("get_queue_engine", "Get queue's AI engine"),
|
|
55
|
+
ToolInfo("create_queue", "Create a queue"),
|
|
56
|
+
ToolInfo("update_queue", "Update queue settings"),
|
|
57
|
+
ToolInfo("get_queue_template_names", "List available queue templates"),
|
|
58
|
+
ToolInfo("create_queue_from_template", "Create queue from template"),
|
|
59
|
+
],
|
|
60
|
+
keywords=["queue", "inbox", "connector"],
|
|
61
|
+
),
|
|
62
|
+
"schemas": ToolCategory(
|
|
63
|
+
name="schemas",
|
|
64
|
+
description="Schema management: define and modify document field structures",
|
|
65
|
+
tools=[
|
|
66
|
+
ToolInfo("get_schema", "Retrieve schema details"),
|
|
67
|
+
ToolInfo("list_schemas", "List all schemas"),
|
|
68
|
+
ToolInfo("update_schema", "Update schema"),
|
|
69
|
+
ToolInfo("create_schema", "Create new schema"),
|
|
70
|
+
ToolInfo("patch_schema", "Add/update/remove schema fields"),
|
|
71
|
+
ToolInfo("get_schema_tree_structure", "Get lightweight schema tree"),
|
|
72
|
+
ToolInfo("prune_schema_fields", "Bulk remove schema fields"),
|
|
73
|
+
],
|
|
74
|
+
keywords=["schema", "field", "datapoint", "section", "multivalue", "tuple"],
|
|
75
|
+
),
|
|
76
|
+
"engines": ToolCategory(
|
|
77
|
+
name="engines",
|
|
78
|
+
description="AI engine management: create and configure extraction/splitting engines",
|
|
79
|
+
tools=[
|
|
80
|
+
ToolInfo("get_engine", "Retrieve engine details"),
|
|
81
|
+
ToolInfo("list_engines", "List all engines"),
|
|
82
|
+
ToolInfo("update_engine", "Update engine settings"),
|
|
83
|
+
ToolInfo("create_engine", "Create new engine"),
|
|
84
|
+
ToolInfo("create_engine_field", "Create engine field mapping"),
|
|
85
|
+
ToolInfo("get_engine_fields", "List engine fields"),
|
|
86
|
+
],
|
|
87
|
+
keywords=["engine", "ai", "extractor", "splitter", "training"],
|
|
88
|
+
),
|
|
89
|
+
"hooks": ToolCategory(
|
|
90
|
+
name="hooks",
|
|
91
|
+
description="Extensions/webhooks: create and manage automation hooks",
|
|
92
|
+
tools=[
|
|
93
|
+
ToolInfo("get_hook", "Retrieve hook details with code"),
|
|
94
|
+
ToolInfo("list_hooks", "List all hooks for a queue"),
|
|
95
|
+
ToolInfo("create_hook", "Create new hook"),
|
|
96
|
+
ToolInfo("update_hook", "Update hook configuration"),
|
|
97
|
+
ToolInfo("list_hook_logs", "View hook execution logs"),
|
|
98
|
+
ToolInfo("list_hook_templates", "List Rossum Store templates"),
|
|
99
|
+
ToolInfo("create_hook_from_template", "Create hook from template"),
|
|
100
|
+
],
|
|
101
|
+
keywords=["hook", "extension", "webhook", "automation", "function", "serverless"],
|
|
102
|
+
),
|
|
103
|
+
"email_templates": ToolCategory(
|
|
104
|
+
name="email_templates",
|
|
105
|
+
description="Email templates: configure automated email responses",
|
|
106
|
+
tools=[
|
|
107
|
+
ToolInfo("get_email_template", "Retrieve email template"),
|
|
108
|
+
ToolInfo("list_email_templates", "List email templates"),
|
|
109
|
+
ToolInfo("create_email_template", "Create email template"),
|
|
110
|
+
],
|
|
111
|
+
keywords=["email", "template", "notification", "rejection"],
|
|
112
|
+
),
|
|
113
|
+
"document_relations": ToolCategory(
|
|
114
|
+
name="document_relations",
|
|
115
|
+
description="Document relations: manage export/einvoice document links",
|
|
116
|
+
tools=[
|
|
117
|
+
ToolInfo("get_document_relation", "Retrieve document relation"),
|
|
118
|
+
ToolInfo("list_document_relations", "List document relations"),
|
|
119
|
+
],
|
|
120
|
+
keywords=["document relation", "export", "einvoice"],
|
|
121
|
+
),
|
|
122
|
+
"relations": ToolCategory(
|
|
123
|
+
name="relations",
|
|
124
|
+
description="Annotation relations: manage edit/attachment/duplicate links",
|
|
125
|
+
tools=[
|
|
126
|
+
ToolInfo("get_relation", "Retrieve relation details"),
|
|
127
|
+
ToolInfo("list_relations", "List annotation relations"),
|
|
128
|
+
],
|
|
129
|
+
keywords=["relation", "duplicate", "attachment", "edit"],
|
|
130
|
+
),
|
|
131
|
+
"rules": ToolCategory(
|
|
132
|
+
name="rules",
|
|
133
|
+
description="Validation rules: manage schema validation rules",
|
|
134
|
+
tools=[
|
|
135
|
+
ToolInfo("get_rule", "Retrieve rule details"),
|
|
136
|
+
ToolInfo("list_rules", "List validation rules"),
|
|
137
|
+
],
|
|
138
|
+
keywords=["rule", "validation", "constraint"],
|
|
139
|
+
),
|
|
140
|
+
"users": ToolCategory(
|
|
141
|
+
name="users",
|
|
142
|
+
description="User management: list users and roles",
|
|
143
|
+
tools=[
|
|
144
|
+
ToolInfo("get_user", "Retrieve user details"),
|
|
145
|
+
ToolInfo("list_users", "List users with filters"),
|
|
146
|
+
ToolInfo("list_user_roles", "List available user roles"),
|
|
147
|
+
],
|
|
148
|
+
keywords=["user", "role", "permission", "token_owner"],
|
|
149
|
+
),
|
|
150
|
+
"workspaces": ToolCategory(
|
|
151
|
+
name="workspaces",
|
|
152
|
+
description="Workspace management: organize queues into workspaces",
|
|
153
|
+
tools=[
|
|
154
|
+
ToolInfo("get_workspace", "Retrieve workspace details"),
|
|
155
|
+
ToolInfo("list_workspaces", "List all workspaces"),
|
|
156
|
+
ToolInfo("create_workspace", "Create new workspace"),
|
|
157
|
+
],
|
|
158
|
+
keywords=["workspace", "organization"],
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_catalog_summary() -> str:
|
|
164
|
+
"""Get a compact text summary of all tool categories for the system prompt."""
|
|
165
|
+
lines = ["Available MCP tool categories (use `list_tool_categories` for details):"]
|
|
166
|
+
for category in TOOL_CATALOG.values():
|
|
167
|
+
tool_names = ", ".join(t.name for t in category.tools)
|
|
168
|
+
lines.append(f"- **{category.name}**: {category.description} [{tool_names}]")
|
|
169
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,36 @@
|
|
|
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."
|
|
25
|
+
)
|
|
26
|
+
async def list_tool_categories() -> list[dict]:
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
"name": category.name,
|
|
30
|
+
"description": category.description,
|
|
31
|
+
"tool_count": len(category.tools),
|
|
32
|
+
"tools": [asdict(tool) for tool in category.tools],
|
|
33
|
+
"keywords": category.keywords,
|
|
34
|
+
}
|
|
35
|
+
for category in TOOL_CATALOG.values()
|
|
36
|
+
]
|
|
@@ -5,9 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from rossum_api.models.document_relation import
|
|
9
|
-
DocumentRelation, # noqa: TC002 - needed at runtime for FastMCP
|
|
10
|
-
)
|
|
8
|
+
from rossum_api.models.document_relation import DocumentRelation
|
|
11
9
|
|
|
12
10
|
if TYPE_CHECKING:
|
|
13
11
|
from fastmcp import FastMCP
|