dtlpymcp 0.1.4__py3-none-any.whl → 0.1.8__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.
dtlpymcp/__init__.py CHANGED
@@ -1,7 +1,4 @@
1
- import os
2
- SOURCES_FILEPATH = os.path.join(os.path.dirname(__file__), "default_sources.json")
3
-
4
1
  from .utils.dtlpy_context import DataloopContext, MCPSource
5
2
 
6
- __version__ = "0.1.4"
3
+ __version__ = "0.1.8"
7
4
 
dtlpymcp/__main__.py CHANGED
@@ -21,11 +21,26 @@ def main():
21
21
  default=None,
22
22
  help="Path to a JSON file with MCP sources to load"
23
23
  )
24
+ start_parser.add_argument(
25
+ "--init-timeout",
26
+ "-t",
27
+ type=float,
28
+ default=30.0,
29
+ help="Timeout in seconds for Dataloop context initialization (default: 30.0)"
30
+ )
31
+ start_parser.add_argument(
32
+ "--log-level",
33
+ "-l",
34
+ type=str,
35
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
36
+ default="DEBUG",
37
+ help="Logging verbosity level (default: DEBUG)"
38
+ )
24
39
 
25
40
  args = parser.parse_args()
26
41
 
27
42
  if args.command == "start":
28
- sys.exit(proxy_main(sources_file=args.sources_file))
43
+ sys.exit(proxy_main(sources_file=args.sources_file, init_timeout=args.init_timeout, log_level=args.log_level))
29
44
  else:
30
45
  parser.print_help()
31
46
  return 1
dtlpymcp/proxy.py CHANGED
@@ -1,101 +1,74 @@
1
- from pydantic_settings import BaseSettings, SettingsConfigDict
2
- from mcp.server.fastmcp import FastMCP, Context
3
- from typing import Any, Optional
4
- from datetime import datetime
5
- import traceback
6
- import logging
7
- import asyncio
8
- import os
9
- from pathlib import Path
10
-
11
- from dtlpymcp.utils.dtlpy_context import DataloopContext, SOURCES_FILEPATH
12
-
13
- # Setup logging to both console and file with timestamp
14
- log_dir = Path.home() / ".dataloop" / "mcplogs"
15
- log_dir.mkdir(parents=True, exist_ok=True)
16
- log_file = log_dir / f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log"
17
-
18
- # Remove any existing handlers
19
- for handler in logging.root.handlers[:]:
20
- logging.root.removeHandler(handler)
21
-
22
- # File handler with timestamp
23
- file_handler = logging.FileHandler(log_file, mode="a", encoding="utf-8")
24
- file_handler.setFormatter(
25
- logging.Formatter(fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
26
- )
27
-
28
- # Console handler (default format)
29
- console_handler = logging.StreamHandler()
30
- console_handler.setFormatter(logging.Formatter(fmt="[%(levelname)s] %(name)s: %(message)s"))
31
-
32
- logging.basicConfig(level=logging.INFO, handlers=[file_handler, console_handler])
33
- logger = logging.getLogger("[DATALOOP-MCP]")
34
-
35
-
36
- class ServerSettings(BaseSettings):
37
- """Settings for the Dataloop MCP server."""
38
-
39
- model_config = SettingsConfigDict(env_prefix="MCP_DATALOOP_")
40
-
41
- def __init__(self, **data):
42
- super().__init__(**data)
43
-
44
-
45
- def create_dataloop_mcp_server(settings: ServerSettings, sources_file: str) -> FastMCP:
46
- """Create a FastMCP server for Dataloop with Bearer token authentication."""
47
- app = FastMCP(
48
- name="Dataloop MCP Server",
49
- instructions="A multi-tenant MCP server for Dataloop with authentication",
50
- stateless_http=True,
51
- debug=True,
52
- )
53
- dl_context = DataloopContext(token=os.environ.get('DATALOOP_API_KEY'),
54
- env=os.environ.get('DATALOOP_ENV', 'prod'),
55
- sources_file=sources_file)
56
-
57
- # Initialize the Dataloop context
58
- asyncio.run(dl_context.initialize())
59
-
60
- @app.tool(description="Test tool for health checks")
61
- async def test(ctx: Context, ping: Any = None) -> dict[str, Any]:
62
- """Health check tool. Returns status ok and echoes ping if provided."""
63
- result = {"status": "ok"}
64
- if ping is not None:
65
- result["ping"] = ping
66
- return result
67
-
68
- logger.info(f"Adding tools from {len(dl_context.mcp_sources)} sources")
69
-
70
- for source in dl_context.mcp_sources:
71
- logger.info(f"Adding tools from source: {source.dpk_name}")
72
- for tool in source.tools:
73
- app._tool_manager._tools[tool.name] = tool
74
- logger.info(f"Registered tool: {tool.name}")
75
-
76
- return app
77
-
78
-
79
- def main(sources_file: Optional[str] = None) -> int:
80
- logger.info("Starting Dataloop MCP server in stdio mode")
81
- try:
82
- settings = ServerSettings()
83
- logger.info("Successfully configured Dataloop MCP server")
84
- except Exception as e:
85
- logger.error(f"Unexpected error during startup:\n{e}")
86
- return 1
87
- try:
88
- if sources_file is None:
89
- sources_file = SOURCES_FILEPATH
90
- logger.info(f"Using sources file: {sources_file}")
91
- mcp_server = create_dataloop_mcp_server(settings, sources_file)
92
- logger.info("Starting Dataloop MCP server in stdio mode")
93
- mcp_server.run(transport="stdio")
94
- return 0
95
- except Exception:
96
- logger.error(f"Failed to start MCP server: {traceback.format_exc()}")
97
- return 1
98
-
99
-
100
- if __name__ == "__main__":
101
- main()
1
+
2
+ from mcp.server.fastmcp import FastMCP, Context
3
+ from typing import Any, Optional
4
+ import traceback
5
+ import os
6
+
7
+ from dtlpymcp.utils.dtlpy_context import DataloopContext
8
+ from dtlpymcp.utils.server_utils import safe_initialize_dataloop_context
9
+ from dtlpymcp.utils.logging_config import setup_logging, get_logger
10
+
11
+ # Get the main logger
12
+ logger = get_logger()
13
+
14
+
15
+ def create_dataloop_mcp_server(sources_file: Optional[str] = None, init_timeout: float = 30.0) -> FastMCP:
16
+ """Create a FastMCP server for Dataloop with Bearer token authentication."""
17
+ app = FastMCP(
18
+ name="Dataloop MCP Server",
19
+ instructions="A multi-tenant MCP server for Dataloop with authentication",
20
+ stateless_http=True,
21
+ debug=True,
22
+ )
23
+
24
+ # Create Dataloop context
25
+ dl_context = DataloopContext(token=os.environ.get('DATALOOP_API_KEY'),
26
+ env=os.environ.get('DATALOOP_ENV', 'prod'),
27
+ sources_file=sources_file)
28
+
29
+ @app.tool(description="Test tool for health checks")
30
+ async def test(ctx: Context, ping: Any = None) -> dict[str, Any]:
31
+ """Health check tool. Returns status ok and echoes ping if provided."""
32
+ result = {"status": "ok"}
33
+ if ping is not None:
34
+ result["ping"] = ping
35
+ return result
36
+
37
+ # Initialize context using the safe utility function
38
+ initialization_success = safe_initialize_dataloop_context(dl_context, app, init_timeout)
39
+
40
+ if not initialization_success:
41
+ logger.info("Server will start without Dataloop tools - they will be available on next restart")
42
+ # Continue without initialization - the server will still work with the test tool
43
+
44
+ return app
45
+
46
+
47
+ def main(sources_file: Optional[str] = None, init_timeout: float = 30.0, log_level: str = "DEBUG") -> int:
48
+ # Setup logging with the specified level
49
+ setup_logging(log_level)
50
+
51
+ logger.info("Starting Dataloop MCP server in stdio mode")
52
+
53
+ # Validate environment variables
54
+ if not os.environ.get('DATALOOP_API_KEY'):
55
+ logger.error("DATALOOP_API_KEY environment variable is required")
56
+ return 1
57
+
58
+ try:
59
+ mcp_server = create_dataloop_mcp_server(sources_file=sources_file, init_timeout=init_timeout)
60
+ logger.info("Dataloop MCP server created successfully")
61
+ logger.info("Starting server in stdio mode...")
62
+ mcp_server.run(transport="stdio")
63
+ return 0
64
+ except KeyboardInterrupt:
65
+ logger.info("Server stopped by user")
66
+ return 0
67
+ except Exception as e:
68
+ logger.error(f"Failed to start MCP server: {e}")
69
+ logger.error(f"Traceback: {traceback.format_exc()}")
70
+ return 1
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
@@ -1,8 +1,7 @@
1
1
  from mcp.server.fastmcp.utilities.func_metadata import ArgModelBase, FuncMetadata
2
2
  from mcp.client.streamable_http import streamablehttp_client
3
- from typing import Any, List, Tuple, Callable, Optional
3
+ from typing import List, Tuple, Callable, Optional
4
4
  from mcp.server.fastmcp.tools.base import Tool
5
- from mcp.server.fastmcp import FastMCP
6
5
  from pydantic import BaseModel, Field
7
6
  from pydantic import create_model
8
7
  from datetime import timedelta
@@ -10,28 +9,13 @@ from mcp import ClientSession
10
9
  import dtlpy as dl
11
10
  import traceback
12
11
  import requests
13
- import logging
14
- import asyncio
15
12
  import time
16
13
  import jwt
17
14
  import json
18
- from dtlpymcp import SOURCES_FILEPATH
19
15
 
20
- # Utility to run async code from sync or async context
21
- # If called from a running event loop, returns a Task (caller must handle it)
22
- # If called from sync, blocks and returns result
16
+ from dtlpymcp.utils.logging_config import get_logger
23
17
 
24
- def run_async(coro):
25
- try:
26
- loop = asyncio.get_running_loop()
27
- except RuntimeError:
28
- # No event loop running
29
- return asyncio.run(coro)
30
- else:
31
- # Already running event loop
32
- return loop.create_task(coro)
33
-
34
- logger = logging.getLogger(__name__)
18
+ logger = get_logger("dtlpy_context")
35
19
 
36
20
 
37
21
  class MCPSource(BaseModel):
@@ -53,31 +37,55 @@ class DataloopContext:
53
37
  self.env = env
54
38
  self.mcp_sources: List[MCPSource] = []
55
39
  logger.info("DataloopContext initialized.")
56
- if sources_file is None:
57
- sources_file = SOURCES_FILEPATH
58
40
  self.sources_file = sources_file
59
41
  self.initialized = False
60
-
61
-
42
+
62
43
  async def initialize(self, force: bool = False):
63
- if not self.initialized or force:
44
+ if not self.initialized or force:
64
45
  await self.register_sources(self.sources_file)
65
46
  self.initialized = True
66
47
 
67
- async def register_sources(self, sources_file: str):
68
- with open(sources_file, "r") as f:
69
- data = json.load(f)
70
- logger.info(f"Loading MCP sources from {sources_file}")
71
- for entry in data:
72
- if not isinstance(entry, dict):
73
- raise ValueError(f"Invalid source entry: {entry}")
74
- logger.info(f"Adding MCP source: {entry.get('dpk_name')}, url: {entry.get('server_url')}")
75
- await self.add_mcp_source(MCPSource(**entry))
48
+ async def register_sources(self, sources_file: str = None):
49
+ if sources_file is None:
50
+ logger.info("Loading MCP sources from all system apps")
51
+ # load all system apps
52
+ filters = dl.Filters(resource='apps')
53
+ filters.add(field="dpkName", values="dataloop-mcp*")
54
+ filters.add(field="scope", values="system")
55
+ apps = list(dl.apps.list(filters=filters).all())
56
+ if len(apps) == 0:
57
+ raise ValueError(f"No app found for DPK name: dataloop-mcp*")
58
+ sources = []
59
+ for app in apps:
60
+ sources.append(
61
+ {
62
+ "dpk_name": app.dpk_name,
63
+ "app_url": next(iter(app.routes.values())),
64
+ "server_url": None,
65
+ "app_jwt": None,
66
+ }
67
+ )
68
+ else:
69
+ logger.info(f"Loading MCP sources from {sources_file}")
70
+
71
+ with open(sources_file, "r") as f:
72
+ sources = json.load(f)
73
+ for entry in sources:
74
+ try:
75
+ if not isinstance(entry, dict):
76
+ raise ValueError(f"Invalid source entry: {entry}")
77
+ logger.info(f"Adding MCP source: {entry.get('dpk_name')}, url: {entry.get('server_url')}")
78
+ await self.add_mcp_source(MCPSource(**entry))
79
+ except Exception as e:
80
+ logger.error(f"Failed to add MCP source: {entry}\n{traceback.format_exc()}")
76
81
 
77
82
  async def add_mcp_source(self, mcp_source: MCPSource):
78
- self.mcp_sources.append(mcp_source)
83
+
79
84
  if mcp_source.server_url is None:
80
- self.load_app_info(mcp_source)
85
+ success = self.load_app_info(mcp_source)
86
+ if not success:
87
+ logger.error(f"Failed to load app info for source {mcp_source.dpk_name}")
88
+ return
81
89
  result = await self.list_source_tools(mcp_source)
82
90
  if result is None:
83
91
  raise ValueError(f"Failed to discover tools for source {mcp_source.dpk_name}")
@@ -111,6 +119,7 @@ class DataloopContext:
111
119
  annotations=None,
112
120
  )
113
121
  mcp_source.tools.append(t)
122
+ self.mcp_sources.append(mcp_source)
114
123
  tool_str = ", ".join([tool.name for tool in mcp_source.tools])
115
124
  logger.info(f"Added MCP source: {mcp_source.dpk_name}, Available tools: {tool_str}")
116
125
 
@@ -122,18 +131,25 @@ class DataloopContext:
122
131
  def token(self, token: str):
123
132
  self._token = token
124
133
 
125
- def load_app_info(self, source: MCPSource) -> None:
134
+ def load_app_info(self, source: MCPSource) -> bool:
126
135
  """
127
136
  Get the source URL and app JWT for a given DPK name using Dataloop SDK.
128
137
  """
129
138
  try:
130
- dl.setenv(self.env)
131
- dl.client_api.token = self.token
132
- dpk = dl.dpks.get(dpk_name=source.dpk_name)
133
- apps_filters = dl.Filters(field='dpkName', values=dpk.name, resource='apps')
134
- app = dl.apps.list(filters=apps_filters).items[0]
135
- logger.info(f"App: {app.name}")
136
- source.app_url = next(iter(app.routes.values()))
139
+ if source.app_url is None:
140
+ dl.setenv(self.env)
141
+ dl.client_api.token = self.token
142
+ filters = dl.Filters(resource='apps')
143
+ filters.add(field="dpkName", values=source.dpk_name)
144
+ filters.add(field="scope", values="system")
145
+ apps = list(dl.apps.list(filters=filters).all())
146
+ if len(apps) == 0:
147
+ raise ValueError(f"No app found for DPK name: {source.dpk_name}")
148
+ if len(apps) > 1:
149
+ logger.warning(f"Multiple apps found for DPK name: {source.dpk_name}, using first one")
150
+ app = apps[0]
151
+ logger.info(f"App: {app.name}")
152
+ source.app_url = next(iter(app.routes.values()))
137
153
  session = requests.Session()
138
154
  response = session.get(source.app_url, headers=dl.client_api.auth)
139
155
  logger.info(f"App route URL: {response.url}")
@@ -141,7 +157,8 @@ class DataloopContext:
141
157
  source.app_jwt = session.cookies.get("JWT-APP")
142
158
  except Exception:
143
159
  logger.error(f"Failed getting app info: {traceback.format_exc()}")
144
- raise ValueError(f"Failed getting app info: {traceback.format_exc()}")
160
+ return False
161
+ return True
145
162
 
146
163
  @staticmethod
147
164
  def is_expired(app_jwt: str) -> bool:
@@ -163,18 +180,16 @@ class DataloopContext:
163
180
  """
164
181
  Get the APP_JWT from the request headers or refresh if expired.
165
182
  """
183
+ if source.app_url is None:
184
+ raise ValueError("App URL is missing. Please set the app URL.")
166
185
  if source.app_jwt is None or self.is_expired(source.app_jwt):
167
186
  try:
168
187
  session = requests.Session()
169
188
  response = session.get(source.app_url, headers={'authorization': 'Bearer ' + token})
170
189
  source.app_jwt = session.cookies.get("JWT-APP")
171
190
  except Exception:
172
- logger.error(f"Failed getting app JWT from cookies\n{traceback.format_exc()}")
173
191
  raise Exception(f"Failed getting app JWT from cookies\n{traceback.format_exc()}") from None
174
192
  if not source.app_jwt:
175
- logger.error(
176
- "APP_JWT is missing. Please set the APP_JWT environment variable or ensure authentication is working."
177
- )
178
193
  raise ValueError(
179
194
  "APP_JWT is missing. Please set the APP_JWT environment variable or ensure authentication is working."
180
195
  )
@@ -195,8 +210,7 @@ class DataloopContext:
195
210
  if source.server_url is None:
196
211
  logger.error("DataloopContext required for DPK servers")
197
212
  raise ValueError("DataloopContext required for DPK servers")
198
- headers = {"Cookie": f"JWT-APP={source.app_jwt}",
199
- "x-dl-info": f"{self.token}"}
213
+ headers = {"Cookie": f"JWT-APP={source.app_jwt}", "x-dl-info": f"{self.token}"}
200
214
  async with streamablehttp_client(source.server_url, headers=headers) as (read, write, _):
201
215
  async with ClientSession(read, write, read_timeout_seconds=timedelta(seconds=60)) as session:
202
216
  await session.initialize()
@@ -0,0 +1,61 @@
1
+ """
2
+ Centralized logging configuration for dtlpymcp.
3
+ Handles file and console logging with configurable verbosity levels.
4
+ """
5
+
6
+ import logging
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+
10
+
11
+ def setup_logging(log_level: str = "DEBUG") -> None:
12
+ """
13
+ Set up logging configuration for the entire dtlpymcp application.
14
+
15
+ Args:
16
+ log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
17
+ """
18
+ # Convert string level to logging constant
19
+ level = getattr(logging, log_level.upper(), logging.DEBUG)
20
+
21
+ # Setup logging directory and file
22
+ log_dir = Path.home() / ".dataloop" / "mcplogs"
23
+ log_dir.mkdir(parents=True, exist_ok=True)
24
+ log_file = log_dir / f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log"
25
+
26
+ # Remove any existing handlers
27
+ for handler in logging.root.handlers[:]:
28
+ logging.root.removeHandler(handler)
29
+
30
+ # File handler with timestamp
31
+ file_handler = logging.FileHandler(log_file, mode="a", encoding="utf-8")
32
+ file_handler.setFormatter(
33
+ logging.Formatter(fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
34
+ )
35
+
36
+ # Console handler (default format)
37
+ console_handler = logging.StreamHandler()
38
+ console_handler.setFormatter(logging.Formatter(fmt="[%(levelname)s] %(name)s: %(message)s"))
39
+
40
+ # Configure root logger
41
+ logging.basicConfig(level=level, handlers=[file_handler, console_handler])
42
+
43
+ # Get the main logger
44
+ logger = logging.getLogger("dtlpymcp")
45
+ logger.info(f"Logging configured with level: {log_level.upper()}")
46
+ logger.info(f"Log file: {log_file}")
47
+
48
+
49
+ def get_logger(name: str = None) -> logging.Logger:
50
+ """
51
+ Get a logger instance with the specified name.
52
+
53
+ Args:
54
+ name: Logger name (optional, defaults to "dtlpymcp")
55
+
56
+ Returns:
57
+ logging.Logger: Configured logger instance
58
+ """
59
+ if name is None:
60
+ return logging.getLogger("dtlpymcp")
61
+ return logging.getLogger(f"dtlpymcp.{name}")
@@ -0,0 +1,91 @@
1
+ """
2
+ Utility functions for Dataloop MCP server initialization and management.
3
+ """
4
+
5
+ import asyncio
6
+ from mcp.server.fastmcp import FastMCP
7
+ from dtlpymcp.utils.dtlpy_context import DataloopContext
8
+ from dtlpymcp.utils.logging_config import get_logger
9
+
10
+ logger = get_logger("server_utils")
11
+
12
+
13
+ def run_async(coro):
14
+ try:
15
+ loop = asyncio.get_running_loop()
16
+ except RuntimeError:
17
+ # No event loop running
18
+ return asyncio.run(coro)
19
+ else:
20
+ # Already running event loop
21
+ return loop.create_task(coro)
22
+
23
+
24
+ async def initialize_dataloop_context(dl_context: DataloopContext, app: FastMCP, init_timeout: float = 30.0) -> bool:
25
+ """
26
+ Initialize Dataloop context with timeout protection.
27
+
28
+ Args:
29
+ dl_context: The DataloopContext instance to initialize
30
+ app: The FastMCP app instance to register tools with
31
+ init_timeout: Timeout in seconds for initialization
32
+
33
+ Returns:
34
+ bool: True if initialization succeeded, False otherwise
35
+ """
36
+ try:
37
+ logger.info("Initializing Dataloop context...")
38
+ # Use asyncio.wait_for for timeout protection
39
+ await asyncio.wait_for(dl_context.initialize(), timeout=init_timeout)
40
+ logger.info("Dataloop context initialized successfully")
41
+
42
+ # Register tools after initialization
43
+ logger.info(f"Adding tools from {len(dl_context.mcp_sources)} sources")
44
+ for source in dl_context.mcp_sources:
45
+ logger.info(f"Adding tools from source: {source.dpk_name}")
46
+ for tool in source.tools:
47
+ app._tool_manager._tools[tool.name] = tool
48
+ logger.info(f"Registered tool: {tool.name}")
49
+
50
+ return True
51
+
52
+ except asyncio.TimeoutError:
53
+ logger.error("Timeout during Dataloop context initialization")
54
+ return False
55
+ except Exception as e:
56
+ logger.error(f"Failed to initialize Dataloop context: {e}")
57
+ return False
58
+
59
+
60
+ def safe_initialize_dataloop_context(dl_context: DataloopContext, app: FastMCP, init_timeout: float = 30.0) -> bool:
61
+ """
62
+ Safely initialize Dataloop context using run_async utility.
63
+
64
+ Args:
65
+ dl_context: The DataloopContext instance to initialize
66
+ app: The FastMCP app instance to register tools with
67
+ init_timeout: Timeout in seconds for initialization
68
+
69
+ Returns:
70
+ bool: True if initialization succeeded, False otherwise
71
+ """
72
+ try:
73
+ # Use the run_async utility to handle async execution properly
74
+ result = run_async(initialize_dataloop_context(dl_context, app, init_timeout))
75
+
76
+ # Handle different return types from run_async
77
+ if hasattr(result, 'done'):
78
+ # It's a task, wait for it to complete
79
+ while not result.done():
80
+ pass
81
+ return result.result()
82
+ elif asyncio.iscoroutine(result):
83
+ # It's a coroutine, run it
84
+ return asyncio.run(result)
85
+ else:
86
+ # It's already the result (bool)
87
+ return result
88
+
89
+ except Exception as e:
90
+ logger.error(f"Failed to initialize during server creation: {e}")
91
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtlpymcp
3
- Version: 0.1.4
3
+ Version: 0.1.8
4
4
  Summary: STDIO MCP proxy server for Dataloop platform.
5
5
  Author-email: Your Name <your.email@example.com>
6
6
  Classifier: Programming Language :: Python :: 3.10
@@ -30,7 +30,14 @@ pip install git+<repository-url>
30
30
  You can run the proxy server via CLI:
31
31
 
32
32
  ```shell
33
+ # Basic usage
33
34
  dtlpymcp start
35
+
36
+ # With custom sources file
37
+ dtlpymcp start --sources-file /path/to/sources.json
38
+
39
+ # With custom initialization timeout (default: 30 seconds)
40
+ dtlpymcp start --init-timeout 60.0
34
41
  ```
35
42
 
36
43
  Or using Python module syntax:
@@ -45,6 +52,14 @@ python -m dtlpymcp start
45
52
  - Install dependencies with `pip install -e .`
46
53
  - Run tests with `pytest`
47
54
 
55
+ ## Architecture
56
+
57
+ The server uses a modular architecture with utilities for safe async initialization:
58
+
59
+ - `dtlpymcp/proxy.py` - Main server implementation using FastMCP
60
+ - `dtlpymcp/utils/server_utils.py` - Safe async initialization utilities
61
+ - `dtlpymcp/utils/dtlpy_context.py` - Dataloop context management
62
+
48
63
  ## Cursor MCP Integration
49
64
 
50
65
  To add this MCP to Cursor, add the following to your configuration:
@@ -0,0 +1,11 @@
1
+ dtlpymcp/__init__.py,sha256=6ARRnx35x-dKohwr72GdxSPqO6JfXU5rDFhM1h-GYDY,88
2
+ dtlpymcp/__main__.py,sha256=BRLHkOrX5Ayea3mMHo7pkY8URnVHzIJfKgWnWr6SdXo,1402
3
+ dtlpymcp/proxy.py,sha256=yL1pa-yDG6snwxsT7PzMIzjRbeV2bEgayJhXLRYzbK0,2815
4
+ dtlpymcp/utils/dtlpy_context.py,sha256=Y4o40ekeZmp63pJ1LwHy5hholkTmpbcg3LoQXd05xac,10553
5
+ dtlpymcp/utils/logging_config.py,sha256=R9eUByUdr3n7Tv9YrCQxH-LyspEjaa5kzR1p8F13q08,2082
6
+ dtlpymcp/utils/server_utils.py,sha256=M-OxGqFqAiGC3xAcNw0Ifkyno7pLInRHA_9LJJI7eQs,3220
7
+ dtlpymcp-0.1.8.dist-info/METADATA,sha256=W10TSsIYzXmapg9dkPk-2dB3mP--_admyvewYU1DdHw,2189
8
+ dtlpymcp-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ dtlpymcp-0.1.8.dist-info/entry_points.txt,sha256=6hRVZNTjQevj7erwt9dAOURtPVrSrYu6uHXhAlhTaXQ,52
10
+ dtlpymcp-0.1.8.dist-info/top_level.txt,sha256=z85v20pIEnY3cBaWgwhU3EZS4WAZRywejhIutwd-iHk,9
11
+ dtlpymcp-0.1.8.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- [
2
- {
3
- "dpk_name": "dataloop-mcp-donna",
4
- "app_url": null,
5
- "server_url": null,
6
- "app_jwt": null
7
- },
8
- {
9
- "dpk_name": "dataloop-mcp-apis",
10
- "app_url": null,
11
- "server_url": null,
12
- "app_jwt": null
13
- },
14
- {
15
- "dpk_name": "dataloop-mcp-debug",
16
- "app_url": null,
17
- "server_url": null,
18
- "app_jwt": null
19
- }
20
- ]
@@ -1,10 +0,0 @@
1
- dtlpymcp/__init__.py,sha256=jz6jKLs8djD6u5xy04_NQO7hgKyFea_oPa-yaiQQq0M,185
2
- dtlpymcp/__main__.py,sha256=1lo4qoZAoUHl9rkt1YGB2kCpKg5cIrTSBSrznxyk6F4,884
3
- dtlpymcp/default_sources.json,sha256=Es3XZcdMpe6o5FyOoqW9FG_oUjC5gkNbBC6N9eBqpQs,418
4
- dtlpymcp/proxy.py,sha256=pE-NAJI0A9wKcBJJt18_aSy1BskZKoxaK3f6VW7u44A,3608
5
- dtlpymcp/utils/dtlpy_context.py,sha256=0NW5FKFpzUl6gTuJGPPPMqklnNuecgBdwLdW-68YUXE,9752
6
- dtlpymcp-0.1.4.dist-info/METADATA,sha256=_CYzt19o00e_UIw9SeiQXW7lKp5q1Bq2xSSbyF7W3Yc,1677
7
- dtlpymcp-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- dtlpymcp-0.1.4.dist-info/entry_points.txt,sha256=6hRVZNTjQevj7erwt9dAOURtPVrSrYu6uHXhAlhTaXQ,52
9
- dtlpymcp-0.1.4.dist-info/top_level.txt,sha256=z85v20pIEnY3cBaWgwhU3EZS4WAZRywejhIutwd-iHk,9
10
- dtlpymcp-0.1.4.dist-info/RECORD,,