atlan-application-sdk 0.1.1rc44__py3-none-any.whl → 0.1.1rc46__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.
- application_sdk/activities/lock_management.py +43 -42
- application_sdk/application/__init__.py +55 -15
- application_sdk/constants.py +6 -0
- application_sdk/decorators/mcp_tool.py +63 -0
- application_sdk/interceptors/lock.py +10 -5
- application_sdk/server/mcp/__init__.py +4 -0
- application_sdk/server/mcp/models.py +11 -0
- application_sdk/server/mcp/server.py +96 -0
- application_sdk/version.py +1 -1
- {atlan_application_sdk-0.1.1rc44.dist-info → atlan_application_sdk-0.1.1rc46.dist-info}/METADATA +1 -1
- {atlan_application_sdk-0.1.1rc44.dist-info → atlan_application_sdk-0.1.1rc46.dist-info}/RECORD +14 -10
- {atlan_application_sdk-0.1.1rc44.dist-info → atlan_application_sdk-0.1.1rc46.dist-info}/WHEEL +0 -0
- {atlan_application_sdk-0.1.1rc44.dist-info → atlan_application_sdk-0.1.1rc46.dist-info}/licenses/LICENSE +0 -0
- {atlan_application_sdk-0.1.1rc44.dist-info → atlan_application_sdk-0.1.1rc46.dist-info}/licenses/NOTICE +0 -0
|
@@ -5,15 +5,15 @@ allowing the workflow to orchestrate locking without hitting Temporal's
|
|
|
5
5
|
deadlock timeout.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import asyncio
|
|
9
8
|
import random
|
|
10
9
|
from typing import Any, Dict
|
|
11
10
|
|
|
12
11
|
from temporalio import activity
|
|
12
|
+
from temporalio.exceptions import ApplicationError
|
|
13
13
|
|
|
14
14
|
from application_sdk.clients.redis import RedisClientAsync
|
|
15
15
|
from application_sdk.common.error_codes import ActivityError
|
|
16
|
-
from application_sdk.constants import APPLICATION_NAME
|
|
16
|
+
from application_sdk.constants import APPLICATION_NAME
|
|
17
17
|
from application_sdk.observability.logger_adaptor import get_logger
|
|
18
18
|
|
|
19
19
|
logger = get_logger(__name__)
|
|
@@ -45,40 +45,41 @@ async def acquire_distributed_lock(
|
|
|
45
45
|
"""
|
|
46
46
|
# Input validation
|
|
47
47
|
if max_locks <= 0:
|
|
48
|
-
raise
|
|
49
|
-
f"{ActivityError.LOCK_ACQUISITION_ERROR}: max_locks must be greater than 0, got {max_locks}"
|
|
48
|
+
raise ApplicationError(
|
|
49
|
+
f"{ActivityError.LOCK_ACQUISITION_ERROR}: max_locks must be greater than 0, got {max_locks}",
|
|
50
|
+
non_retryable=True,
|
|
51
|
+
)
|
|
52
|
+
slot = random.randint(0, max_locks - 1)
|
|
53
|
+
resource_id = f"{APPLICATION_NAME}:{lock_name}:{slot}"
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
async with RedisClientAsync() as redis_client:
|
|
57
|
+
# Acquire lock - connection will stay open until context exits
|
|
58
|
+
acquired = await redis_client._acquire_lock(
|
|
59
|
+
resource_id, owner_id, ttl_seconds
|
|
60
|
+
)
|
|
61
|
+
if acquired:
|
|
62
|
+
logger.info(f"Lock acquired for slot {slot}, resource: {resource_id}")
|
|
63
|
+
return {
|
|
64
|
+
"status": True,
|
|
65
|
+
"slot_id": slot,
|
|
66
|
+
"resource_id": resource_id,
|
|
67
|
+
"owner_id": owner_id,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
raise ActivityError(
|
|
71
|
+
f"{ActivityError.LOCK_ACQUISITION_ERROR}: Lock not acquired for {resource_id}, will retry after some time"
|
|
72
|
+
)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
# Redis connection or operation failed - propagate as activity error
|
|
75
|
+
if isinstance(e, (ActivityError)):
|
|
76
|
+
raise e
|
|
77
|
+
logger.error(f"Redis error during lock acquisition: {e}")
|
|
78
|
+
raise ApplicationError(
|
|
79
|
+
f"Redis error during lock acquisition for {resource_id}, error: {e}",
|
|
80
|
+
non_retryable=True,
|
|
81
|
+
type=type(e).__name__,
|
|
50
82
|
)
|
|
51
|
-
|
|
52
|
-
async with RedisClientAsync() as redis_client:
|
|
53
|
-
while True:
|
|
54
|
-
slot = random.randint(0, max_locks - 1)
|
|
55
|
-
resource_id = f"{APPLICATION_NAME}:{lock_name}:{slot}"
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
# Acquire lock - connection will stay open until context exits
|
|
59
|
-
acquired = await redis_client._acquire_lock(
|
|
60
|
-
resource_id, owner_id, ttl_seconds
|
|
61
|
-
)
|
|
62
|
-
if acquired:
|
|
63
|
-
logger.info(
|
|
64
|
-
f"Lock acquired for slot {slot}, resource: {resource_id}"
|
|
65
|
-
)
|
|
66
|
-
return {
|
|
67
|
-
"slot_id": slot,
|
|
68
|
-
"resource_id": resource_id,
|
|
69
|
-
"owner_id": owner_id,
|
|
70
|
-
}
|
|
71
|
-
# If not acquired, continue retrying (lock held by another owner)
|
|
72
|
-
|
|
73
|
-
except Exception as e:
|
|
74
|
-
# Redis connection or operation failed - propagate as activity error
|
|
75
|
-
logger.error(f"Redis error during lock acquisition: {e}")
|
|
76
|
-
raise ActivityError(
|
|
77
|
-
f"{ActivityError.LOCK_ACQUISITION_ERROR}: Redis error during lock acquisition for {resource_id}"
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# Wait before retrying
|
|
81
|
-
await asyncio.sleep(LOCK_RETRY_INTERVAL)
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
@activity.defn
|
|
@@ -94,8 +95,8 @@ async def release_distributed_lock(
|
|
|
94
95
|
Returns:
|
|
95
96
|
True if lock was released successfully, False otherwise
|
|
96
97
|
"""
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
try:
|
|
99
|
+
async with RedisClientAsync() as redis_client:
|
|
99
100
|
released, result = await redis_client._release_lock(resource_id, owner_id)
|
|
100
101
|
if released:
|
|
101
102
|
logger.info(
|
|
@@ -103,8 +104,8 @@ async def release_distributed_lock(
|
|
|
103
104
|
)
|
|
104
105
|
return released
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Redis error during lock release for {resource_id}: {e}")
|
|
109
|
+
# Don't raise exception for lock release failures - log and return False
|
|
110
|
+
# Lock release is best-effort and shouldn't fail the workflow
|
|
111
|
+
return False
|
|
@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple, Type
|
|
|
4
4
|
from application_sdk.activities import ActivitiesInterface
|
|
5
5
|
from application_sdk.clients.base import BaseClient
|
|
6
6
|
from application_sdk.clients.utils import get_workflow_client
|
|
7
|
+
from application_sdk.constants import ENABLE_MCP
|
|
7
8
|
from application_sdk.events.models import EventRegistration
|
|
8
9
|
from application_sdk.handlers.base import BaseHandler
|
|
9
10
|
from application_sdk.observability.logger_adaptor import get_logger
|
|
@@ -29,7 +30,7 @@ class BaseApplication:
|
|
|
29
30
|
self,
|
|
30
31
|
name: str,
|
|
31
32
|
server: Optional[ServerInterface] = None,
|
|
32
|
-
application_manifest: Optional[
|
|
33
|
+
application_manifest: Optional[Dict[str, Any]] = None,
|
|
33
34
|
client_class: Optional[Type[BaseClient]] = None,
|
|
34
35
|
handler_class: Optional[Type[BaseHandler]] = None,
|
|
35
36
|
):
|
|
@@ -39,6 +40,9 @@ class BaseApplication:
|
|
|
39
40
|
Args:
|
|
40
41
|
name (str): The name of the application.
|
|
41
42
|
server (ServerInterface): The server class for the application.
|
|
43
|
+
application_manifest (Optional[Dict[str, Any]]): Application manifest configuration.
|
|
44
|
+
client_class (Optional[Type[BaseClient]]): Client class for the application.
|
|
45
|
+
handler_class (Optional[Type[BaseHandler]]): Handler class for the application.
|
|
42
46
|
"""
|
|
43
47
|
self.application_name = name
|
|
44
48
|
|
|
@@ -49,14 +53,21 @@ class BaseApplication:
|
|
|
49
53
|
|
|
50
54
|
self.workflow_client = get_workflow_client(application_name=name)
|
|
51
55
|
|
|
52
|
-
self.application_manifest: Dict[str, Any] = application_manifest
|
|
56
|
+
self.application_manifest: Optional[Dict[str, Any]] = application_manifest
|
|
53
57
|
self.bootstrap_event_registration()
|
|
54
58
|
|
|
55
59
|
self.client_class = client_class or BaseClient
|
|
56
60
|
self.handler_class = handler_class or BaseHandler
|
|
57
61
|
|
|
62
|
+
# MCP configuration
|
|
63
|
+
self.mcp_server: Optional["MCPServer"] = None
|
|
64
|
+
if ENABLE_MCP:
|
|
65
|
+
from application_sdk.server.mcp import MCPServer
|
|
66
|
+
|
|
67
|
+
self.mcp_server = MCPServer(application_name=name)
|
|
68
|
+
|
|
58
69
|
def bootstrap_event_registration(self):
|
|
59
|
-
self.event_subscriptions = {}
|
|
70
|
+
self.event_subscriptions: Dict[str, EventWorkflowTrigger] = {}
|
|
60
71
|
if self.application_manifest is None:
|
|
61
72
|
logger.warning("No application manifest found, skipping event registration")
|
|
62
73
|
return
|
|
@@ -122,8 +133,8 @@ class BaseApplication:
|
|
|
122
133
|
]
|
|
123
134
|
workflow_activities = []
|
|
124
135
|
for workflow_class, activities_class in workflow_and_activities_classes:
|
|
125
|
-
workflow_activities.extend(
|
|
126
|
-
workflow_class.get_activities(activities_class())
|
|
136
|
+
workflow_activities.extend( # type: ignore
|
|
137
|
+
workflow_class.get_activities(activities_class()) # type: ignore
|
|
127
138
|
)
|
|
128
139
|
|
|
129
140
|
self.worker = Worker(
|
|
@@ -134,6 +145,13 @@ class BaseApplication:
|
|
|
134
145
|
activity_executor=activity_executor,
|
|
135
146
|
)
|
|
136
147
|
|
|
148
|
+
# Register MCP tools if ENABLED_MCP is True and an MCP server is initialized
|
|
149
|
+
if self.mcp_server:
|
|
150
|
+
logger.info("Registering MCP tools from workflow and activities classes")
|
|
151
|
+
await self.mcp_server.register_tools( # type: ignore
|
|
152
|
+
workflow_and_activities_classes=workflow_and_activities_classes
|
|
153
|
+
)
|
|
154
|
+
|
|
137
155
|
async def start_workflow(self, workflow_args, workflow_class) -> Any:
|
|
138
156
|
"""
|
|
139
157
|
Start a new workflow execution.
|
|
@@ -147,7 +165,7 @@ class BaseApplication:
|
|
|
147
165
|
"""
|
|
148
166
|
if self.workflow_client is None:
|
|
149
167
|
raise ValueError("Workflow client not initialized")
|
|
150
|
-
return await self.workflow_client.start_workflow(workflow_args, workflow_class)
|
|
168
|
+
return await self.workflow_client.start_workflow(workflow_args, workflow_class) # type: ignore
|
|
151
169
|
|
|
152
170
|
async def start_worker(self, daemon: bool = True):
|
|
153
171
|
"""
|
|
@@ -162,39 +180,61 @@ class BaseApplication:
|
|
|
162
180
|
|
|
163
181
|
async def setup_server(
|
|
164
182
|
self,
|
|
165
|
-
workflow_class,
|
|
183
|
+
workflow_class: Type[WorkflowInterface],
|
|
166
184
|
ui_enabled: bool = True,
|
|
167
185
|
has_configmap: bool = False,
|
|
168
186
|
):
|
|
169
187
|
"""
|
|
170
|
-
|
|
188
|
+
Set up FastAPI server and automatically mount MCP if enabled.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
workflow_class (WorkflowInterface): The workflow class for the application.
|
|
192
|
+
ui_enabled (bool): Whether to enable the UI.
|
|
193
|
+
has_configmap (bool): Whether to enable the configmap.
|
|
171
194
|
"""
|
|
172
195
|
if self.workflow_client is None:
|
|
173
196
|
await self.workflow_client.load()
|
|
174
197
|
|
|
175
|
-
|
|
198
|
+
mcp_http_app: Optional[Any] = None
|
|
199
|
+
lifespan: Optional[Any] = None
|
|
200
|
+
|
|
201
|
+
if self.mcp_server:
|
|
202
|
+
try:
|
|
203
|
+
mcp_http_app = await self.mcp_server.get_http_app()
|
|
204
|
+
lifespan = mcp_http_app.lifespan
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.warning(f"Failed to get MCP HTTP app: {e}")
|
|
207
|
+
|
|
176
208
|
self.server = APIServer(
|
|
209
|
+
lifespan=lifespan,
|
|
177
210
|
workflow_client=self.workflow_client,
|
|
178
211
|
ui_enabled=ui_enabled,
|
|
179
212
|
handler=self.handler_class(client=self.client_class()),
|
|
180
213
|
has_configmap=has_configmap,
|
|
181
214
|
)
|
|
182
215
|
|
|
216
|
+
# Mount MCP at root
|
|
217
|
+
if mcp_http_app:
|
|
218
|
+
try:
|
|
219
|
+
self.server.app.mount("", mcp_http_app) # Mount at root
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.warning(f"Failed to mount MCP HTTP app: {e}")
|
|
222
|
+
|
|
223
|
+
# Register event-based workflows if any
|
|
183
224
|
if self.event_subscriptions:
|
|
184
225
|
for event_trigger in self.event_subscriptions.values():
|
|
185
|
-
if event_trigger.workflow_class is None:
|
|
226
|
+
if event_trigger.workflow_class is None: # type: ignore
|
|
186
227
|
raise ValueError(
|
|
187
228
|
f"Workflow class not set for event trigger {event_trigger.event_id}"
|
|
188
229
|
)
|
|
189
230
|
|
|
190
|
-
self.server.register_workflow(
|
|
191
|
-
workflow_class=event_trigger.workflow_class,
|
|
231
|
+
self.server.register_workflow( # type: ignore
|
|
232
|
+
workflow_class=event_trigger.workflow_class, # type: ignore
|
|
192
233
|
triggers=[event_trigger],
|
|
193
234
|
)
|
|
194
235
|
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
self.server.register_workflow(
|
|
236
|
+
# Register the main workflow (HTTP POST /start endpoint)
|
|
237
|
+
self.server.register_workflow( # type: ignore
|
|
198
238
|
workflow_class=workflow_class,
|
|
199
239
|
triggers=[HttpWorkflowTrigger()],
|
|
200
240
|
)
|
application_sdk/constants.py
CHANGED
|
@@ -255,3 +255,9 @@ REDIS_SENTINEL_HOSTS = os.getenv("REDIS_SENTINEL_HOSTS", "")
|
|
|
255
255
|
IS_LOCKING_DISABLED = os.getenv("IS_LOCKING_DISABLED", "true").lower() == "true"
|
|
256
256
|
#: Retry interval for lock acquisition
|
|
257
257
|
LOCK_RETRY_INTERVAL = int(os.getenv("LOCK_RETRY_INTERVAL", "5"))
|
|
258
|
+
|
|
259
|
+
# MCP Configuration
|
|
260
|
+
#: Flag to indicate if MCP should be enabled or not. Turning this to true will setup an MCP server along
|
|
261
|
+
#: with the application.
|
|
262
|
+
ENABLE_MCP = os.getenv("ENABLE_MCP", "false").lower() == "true"
|
|
263
|
+
MCP_METADATA_KEY = "__atlan_application_sdk_mcp_metadata"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP tool decorator for marking activities as MCP tools.
|
|
3
|
+
|
|
4
|
+
This module provides the @mcp_tool decorator that developers use to mark
|
|
5
|
+
activities for automatic exposure via Model Context Protocol.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Callable, Optional
|
|
9
|
+
|
|
10
|
+
from application_sdk.constants import MCP_METADATA_KEY
|
|
11
|
+
from application_sdk.server.mcp import MCPMetadata
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def mcp_tool(
|
|
15
|
+
name: Optional[str] = None,
|
|
16
|
+
description: Optional[str] = None,
|
|
17
|
+
visible: bool = True,
|
|
18
|
+
*args,
|
|
19
|
+
**kwargs,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Decorator to mark functions as MCP tools.
|
|
23
|
+
|
|
24
|
+
Use this decorator to mark any function as an MCP tool. You can additionally use the `visible`
|
|
25
|
+
parameter to control whether the tool is visible at runtime or not.
|
|
26
|
+
|
|
27
|
+
Function parameters that are Pydantic models will be automatically converted into correct JSON schema
|
|
28
|
+
for the tool specification. This is handled by the underlying FastMCP server implementation.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
name(Optional[str]): The name of the tool. Defaults to the function name.
|
|
32
|
+
description(Optional[str]): The description of the tool. Defaults to the function docstring.
|
|
33
|
+
visible(bool): Whether the MCP tool is visible at runtime or not. Defaults to True.
|
|
34
|
+
*args: Additional arguments to pass to the tool.
|
|
35
|
+
**kwargs: Additional keyword arguments to pass to the tool.
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
>>> @mcp_tool(name="add_numbers", description="Add two numbers", visible=True)
|
|
39
|
+
>>> def add_numbers(self, a: int, b: int) -> int:
|
|
40
|
+
>>> return a + b
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
>>> # Use with Temporal activity decorator
|
|
44
|
+
>>> @activity.defn
|
|
45
|
+
>>> @mcp_tool(name="get_weather", description="Get the weather for a given city")
|
|
46
|
+
>>> async def get_weather(self, city: str) -> str:
|
|
47
|
+
>>> # ... activity implementation unchanged ...
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
|
|
51
|
+
mcp_metadata = MCPMetadata(
|
|
52
|
+
name=name if name else f.__name__,
|
|
53
|
+
description=description if description else f.__doc__,
|
|
54
|
+
visible=visible,
|
|
55
|
+
args=args,
|
|
56
|
+
kwargs=kwargs,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
setattr(f, MCP_METADATA_KEY, mcp_metadata)
|
|
60
|
+
|
|
61
|
+
return f
|
|
62
|
+
|
|
63
|
+
return decorator
|
|
@@ -22,6 +22,7 @@ from application_sdk.constants import (
|
|
|
22
22
|
APPLICATION_NAME,
|
|
23
23
|
IS_LOCKING_DISABLED,
|
|
24
24
|
LOCK_METADATA_KEY,
|
|
25
|
+
LOCK_RETRY_INTERVAL,
|
|
25
26
|
)
|
|
26
27
|
from application_sdk.observability.logger_adaptor import get_logger
|
|
27
28
|
|
|
@@ -106,13 +107,17 @@ class RedisLockOutboundInterceptor(WorkflowOutboundInterceptor):
|
|
|
106
107
|
lock_result = None
|
|
107
108
|
|
|
108
109
|
try:
|
|
109
|
-
# Step 1: Acquire lock via dedicated activity
|
|
110
|
-
|
|
111
|
-
lock_result = await workflow.
|
|
110
|
+
# Step 1: Acquire lock via dedicated activity with Temporal retry policy
|
|
111
|
+
schedule_to_close_timeout = workflow.info().execution_timeout
|
|
112
|
+
lock_result = await workflow.execute_local_activity(
|
|
112
113
|
"acquire_distributed_lock",
|
|
113
114
|
args=[lock_name, max_locks, ttl_seconds, owner_id],
|
|
114
|
-
start_to_close_timeout=
|
|
115
|
-
retry_policy=RetryPolicy(
|
|
115
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
116
|
+
retry_policy=RetryPolicy(
|
|
117
|
+
initial_interval=timedelta(seconds=int(LOCK_RETRY_INTERVAL)),
|
|
118
|
+
backoff_coefficient=1.0,
|
|
119
|
+
),
|
|
120
|
+
schedule_to_close_timeout=schedule_to_close_timeout,
|
|
116
121
|
)
|
|
117
122
|
|
|
118
123
|
logger.debug(f"Lock acquired: {lock_result}, executing {input.activity}")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Server implementation using FastMCP for Atlan Application SDK.
|
|
3
|
+
|
|
4
|
+
This module provides the MCPServer class that automatically discovers
|
|
5
|
+
activities marked with @mcp_tool decorators and mounts them on FastAPI
|
|
6
|
+
using streamable HTTP transport.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, List, Optional, Tuple, Type
|
|
10
|
+
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
from fastmcp.server.http import StarletteWithLifespan
|
|
13
|
+
|
|
14
|
+
from application_sdk.activities import ActivitiesInterface
|
|
15
|
+
from application_sdk.constants import MCP_METADATA_KEY
|
|
16
|
+
from application_sdk.observability.logger_adaptor import get_logger
|
|
17
|
+
from application_sdk.server.mcp.models import MCPMetadata
|
|
18
|
+
from application_sdk.workflows import WorkflowInterface
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MCPServer:
|
|
22
|
+
"""
|
|
23
|
+
MCP Server using FastMCP 2.0 with FastAPI mounting capability.
|
|
24
|
+
|
|
25
|
+
This server automatically discovers activities marked with @mcp_tool
|
|
26
|
+
and creates a FastMCP server that can be mounted on FastAPI.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, application_name: str, instructions: Optional[str] = None):
|
|
30
|
+
"""
|
|
31
|
+
Initialize the MCP server.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
application_name (str): Name of the application
|
|
35
|
+
instructions (Optional[str]): Description for the MCP server
|
|
36
|
+
"""
|
|
37
|
+
self.application_name = application_name
|
|
38
|
+
|
|
39
|
+
self.logger = get_logger(__name__)
|
|
40
|
+
|
|
41
|
+
# FastMCP Server
|
|
42
|
+
self.server = FastMCP(
|
|
43
|
+
name=f"{application_name} MCP",
|
|
44
|
+
instructions=instructions,
|
|
45
|
+
on_duplicate_tools="error",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
async def register_tools(
|
|
49
|
+
self,
|
|
50
|
+
workflow_and_activities_classes: List[
|
|
51
|
+
Tuple[Type[WorkflowInterface], Type[ActivitiesInterface]]
|
|
52
|
+
],
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Discover activities marked with @mcp_tool and register them.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
workflow_and_activities_classes: List of (workflow_class, activities_class) tuples
|
|
59
|
+
"""
|
|
60
|
+
activity_methods: List[Callable[..., Any]] = []
|
|
61
|
+
for workflow_class, activities_class in workflow_and_activities_classes:
|
|
62
|
+
activities_instance = activities_class()
|
|
63
|
+
activity_methods.extend(workflow_class.get_activities(activities_instance)) # type: ignore
|
|
64
|
+
|
|
65
|
+
for f in activity_methods:
|
|
66
|
+
mcp_metadata: Optional[MCPMetadata] = getattr(f, MCP_METADATA_KEY, None)
|
|
67
|
+
if not mcp_metadata:
|
|
68
|
+
self.logger.info(
|
|
69
|
+
f"No MCP metadata found on activity method {f.__name__}. Skipping tool registration"
|
|
70
|
+
)
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
if mcp_metadata.visible:
|
|
74
|
+
self.logger.info(
|
|
75
|
+
f"Registering tool {mcp_metadata.name} with description: {mcp_metadata.description}"
|
|
76
|
+
)
|
|
77
|
+
self.server.tool(
|
|
78
|
+
f,
|
|
79
|
+
name=mcp_metadata.name,
|
|
80
|
+
description=mcp_metadata.description,
|
|
81
|
+
*mcp_metadata.args,
|
|
82
|
+
**mcp_metadata.kwargs,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
self.logger.info(
|
|
86
|
+
f"Tool {mcp_metadata.name} is marked as not visible. Skipping tool registration"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
tools = await self.server.get_tools()
|
|
90
|
+
self.logger.info(f"Registered {len(tools)} tools: {list(tools.keys())}")
|
|
91
|
+
|
|
92
|
+
async def get_http_app(self) -> StarletteWithLifespan:
|
|
93
|
+
"""
|
|
94
|
+
Get the HTTP app for the MCP server.
|
|
95
|
+
"""
|
|
96
|
+
return self.server.http_app()
|
application_sdk/version.py
CHANGED
{atlan_application_sdk-0.1.1rc44.dist-info → atlan_application_sdk-0.1.1rc46.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atlan-application-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1rc46
|
|
4
4
|
Summary: Atlan Application SDK is a Python library for developing applications on the Atlan Platform
|
|
5
5
|
Project-URL: Repository, https://github.com/atlanhq/application-sdk
|
|
6
6
|
Project-URL: Documentation, https://github.com/atlanhq/application-sdk/README.md
|
{atlan_application_sdk-0.1.1rc44.dist-info → atlan_application_sdk-0.1.1rc46.dist-info}/RECORD
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
application_sdk/__init__.py,sha256=2e2mvmLJ5dxmJGPELtb33xwP-j6JMdoIuqKycEn7hjg,151
|
|
2
|
-
application_sdk/constants.py,sha256=
|
|
3
|
-
application_sdk/version.py,sha256=
|
|
2
|
+
application_sdk/constants.py,sha256=eLHmH9GukXCKK-u5a4bAqz8BeCOCusCM0eW0Q0Bwjns,10947
|
|
3
|
+
application_sdk/version.py,sha256=TTHbof7z1rqMmnKkkw-wrzn3nRqtDcXfc_kbnHfnflc,88
|
|
4
4
|
application_sdk/worker.py,sha256=i5f0AeKI39IfsLO05QkwC6uMz0zDPSJqP7B2byri1VI,7489
|
|
5
5
|
application_sdk/activities/__init__.py,sha256=QaXLOBYbb0zPOY5kfDQh56qbXQFaYNXOjJ5PCvatiZ4,9530
|
|
6
|
-
application_sdk/activities/lock_management.py,sha256=
|
|
6
|
+
application_sdk/activities/lock_management.py,sha256=oX2qPpfEu_xP0MiaCakVGk9ivZDvG4EddVZag1DuHSE,3976
|
|
7
7
|
application_sdk/activities/.cursor/BUGBOT.md,sha256=FNykX5aMkdOhzgpiGqstOnSp9JN63iR2XP3onU4AGh8,15843
|
|
8
8
|
application_sdk/activities/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
application_sdk/activities/common/models.py,sha256=LIZfWvTtgtbAUvvn-rwrPQgD7fP2J0Gxdxr_ITgw-jM,1243
|
|
@@ -14,7 +14,7 @@ application_sdk/activities/metadata_extraction/rest.py,sha256=47DEQpj8HBSa-_TImW
|
|
|
14
14
|
application_sdk/activities/metadata_extraction/sql.py,sha256=ivIbTrkKAonijQQPfiOigoiXLWtA_-nLUn9lz09lpaU,34725
|
|
15
15
|
application_sdk/activities/query_extraction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
application_sdk/activities/query_extraction/sql.py,sha256=l64cGyTmbtaGcg3qj1YXKyNWiWeRsWPEuQyqW06rxxQ,21165
|
|
17
|
-
application_sdk/application/__init__.py,sha256=
|
|
17
|
+
application_sdk/application/__init__.py,sha256=hb5zBc4zi-10av8Ivbovhb0CEAwNgr3eFlfpRaMKVmI,9861
|
|
18
18
|
application_sdk/application/metadata_extraction/sql.py,sha256=rOd06Wodr4GyzupCYxVSCsNcuNar1rJM66ej9vocNHw,8138
|
|
19
19
|
application_sdk/clients/__init__.py,sha256=C9T84J7V6ZumcoWJPAxdd3tqSmbyciaGBJn-CaCCny0,1341
|
|
20
20
|
application_sdk/clients/atlan.py,sha256=l6yV39fr1006SJFwkOTNDQlbSFlHCZQaUPfdUlzdVEg,5053
|
|
@@ -36,6 +36,7 @@ application_sdk/common/utils.py,sha256=ImCrlyCj5Mj571CVWfqy5MynVVju9xhn1ItSlJoae
|
|
|
36
36
|
application_sdk/common/.cursor/BUGBOT.md,sha256=OkB5TMAEJFzaBfbNb3g9ZDPW2r1krQE_KEuJbytMPuI,12176
|
|
37
37
|
application_sdk/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
application_sdk/decorators/locks.py,sha256=-cdbICCMns3lkqZ4CCQabW1du8cEu9XSWlwzWTTbIPk,1411
|
|
39
|
+
application_sdk/decorators/mcp_tool.py,sha256=uxuc0Qk1A_MMvnwWe19kYGn4cfipvwwkmITR5nBdHP8,2215
|
|
39
40
|
application_sdk/decorators/.cursor/BUGBOT.md,sha256=iiS_41FKaJ4-L2jm9ziEqQhQNGYGNZzHVVk09c2cgN8,11250
|
|
40
41
|
application_sdk/docgen/__init__.py,sha256=Gr_3uVEnSspKd_-R1YRsDABI-iP4170Dvg5jM2oD76A,7352
|
|
41
42
|
application_sdk/docgen/exporters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -66,7 +67,7 @@ application_sdk/inputs/.cursor/BUGBOT.md,sha256=hwKGDbopv3NU0bpC_ElpAPDFcS59GWS3
|
|
|
66
67
|
application_sdk/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
68
|
application_sdk/interceptors/cleanup.py,sha256=JlFcM_2Y5AIEfGTSNe0aoon7eoE68MIXI0rA3LHsSeY,5966
|
|
68
69
|
application_sdk/interceptors/events.py,sha256=TeStWmBbc4v1-dm2DWeKYsUfUhJLR8CtTQhu3TWOZWM,6524
|
|
69
|
-
application_sdk/interceptors/lock.py,sha256=
|
|
70
|
+
application_sdk/interceptors/lock.py,sha256=K1e1p11OYDDTy5TFMHcKXAvY4H86yXgpAZiuncEyH2M,5810
|
|
70
71
|
application_sdk/interceptors/.cursor/BUGBOT.md,sha256=pxmUF2c7dtaXAX8yAa1-LBa6FCrj_uw7aQcHrppjf1A,14570
|
|
71
72
|
application_sdk/observability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
73
|
application_sdk/observability/logger_adaptor.py,sha256=WTqnNg78W2SRGOQVhELVLn6KMRsurkG1kc7essL08Lk,29529
|
|
@@ -89,6 +90,9 @@ application_sdk/server/fastapi/middleware/logmiddleware.py,sha256=CxcPtDmCbSfSZ8
|
|
|
89
90
|
application_sdk/server/fastapi/middleware/metrics.py,sha256=5ddHAIg5sT-u9tB_HHMGL3Cfu2g1rm9z7ksienIr9ks,1563
|
|
90
91
|
application_sdk/server/fastapi/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
92
|
application_sdk/server/fastapi/routers/server.py,sha256=vfHQwZCysThzfeVFNVW1IjuAdL0c1Cs4fULKTBK2eNo,4209
|
|
93
|
+
application_sdk/server/mcp/__init__.py,sha256=29HI-GkNn9wOPsb86Litmv0MhXWFBNVBOivJcNFWZas,154
|
|
94
|
+
application_sdk/server/mcp/models.py,sha256=3jX_3wXBuFs6XX9OG-xoag8VkKYWO_Y5r5-CHcLU5vg,236
|
|
95
|
+
application_sdk/server/mcp/server.py,sha256=HG8tFmcc-f9Wj3vZzs2oRoNJzN1s5hwjnKykSdTXgCQ,3450
|
|
92
96
|
application_sdk/services/__init__.py,sha256=H-5HZEPdr53MUfAggyHqHhRXDRLZFZsxvJgWbr257Ds,465
|
|
93
97
|
application_sdk/services/atlan_storage.py,sha256=TKzXxu0yXeUcmZehwp8PcnQTC4A9w9RlZ0Fl-Xp1bLE,8509
|
|
94
98
|
application_sdk/services/eventstore.py,sha256=X03JzodKByXh8w8nOl658rnnZfMFTj0IkmiLVbd6IN8,6729
|
|
@@ -152,8 +156,8 @@ application_sdk/workflows/metadata_extraction/__init__.py,sha256=jHUe_ZBQ66jx8bg
|
|
|
152
156
|
application_sdk/workflows/metadata_extraction/sql.py,sha256=6ZaVt84n-8U2ZvR9GR7uIJKv5v8CuyQjhlnoRJvDszc,12435
|
|
153
157
|
application_sdk/workflows/query_extraction/__init__.py,sha256=n066_CX5RpJz6DIxGMkKS3eGSRg03ilaCtsqfJWQb7Q,117
|
|
154
158
|
application_sdk/workflows/query_extraction/sql.py,sha256=kT_JQkLCRZ44ZpaC4QvPL6DxnRIIVh8gYHLqRbMI-hA,4826
|
|
155
|
-
atlan_application_sdk-0.1.
|
|
156
|
-
atlan_application_sdk-0.1.
|
|
157
|
-
atlan_application_sdk-0.1.
|
|
158
|
-
atlan_application_sdk-0.1.
|
|
159
|
-
atlan_application_sdk-0.1.
|
|
159
|
+
atlan_application_sdk-0.1.1rc46.dist-info/METADATA,sha256=lFNImf3iOTzlw4bxeegOBO1YNjNfQ3yhAFzluCASQNI,5567
|
|
160
|
+
atlan_application_sdk-0.1.1rc46.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
161
|
+
atlan_application_sdk-0.1.1rc46.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
162
|
+
atlan_application_sdk-0.1.1rc46.dist-info/licenses/NOTICE,sha256=A-XVVGt3KOYuuMmvSMIFkg534F1vHiCggEBp4Ez3wGk,1041
|
|
163
|
+
atlan_application_sdk-0.1.1rc46.dist-info/RECORD,,
|
{atlan_application_sdk-0.1.1rc44.dist-info → atlan_application_sdk-0.1.1rc46.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|