dtlpymcp 0.1.8__tar.gz → 0.1.10__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 (23) hide show
  1. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/PKG-INFO +1 -1
  2. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp/__init__.py +1 -2
  3. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp/__main__.py +9 -12
  4. dtlpymcp-0.1.8/dtlpymcp/proxy.py → dtlpymcp-0.1.10/dtlpymcp/min_proxy.py +36 -35
  5. dtlpymcp-0.1.10/dtlpymcp/proxy.py +174 -0
  6. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp/utils/dtlpy_context.py +9 -7
  7. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp.egg-info/PKG-INFO +1 -1
  8. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp.egg-info/SOURCES.txt +1 -2
  9. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/pyproject.toml +1 -1
  10. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/tests/test_proxy.py +7 -5
  11. dtlpymcp-0.1.8/dtlpymcp/utils/logging_config.py +0 -61
  12. dtlpymcp-0.1.8/dtlpymcp/utils/server_utils.py +0 -91
  13. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/MANIFEST.in +0 -0
  14. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/README.md +0 -0
  15. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp.egg-info/dependency_links.txt +0 -0
  16. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp.egg-info/entry_points.txt +0 -0
  17. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp.egg-info/requires.txt +0 -0
  18. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/dtlpymcp.egg-info/top_level.txt +0 -0
  19. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/setup.cfg +0 -0
  20. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/tests/test_context.py +0 -0
  21. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/tests/test_custom_sources_file.py +0 -0
  22. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/tests/test_list_platform_tools.py +0 -0
  23. {dtlpymcp-0.1.8 → dtlpymcp-0.1.10}/tests/test_run.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtlpymcp
3
- Version: 0.1.8
3
+ Version: 0.1.10
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
@@ -1,4 +1,3 @@
1
1
  from .utils.dtlpy_context import DataloopContext, MCPSource
2
2
 
3
- __version__ = "0.1.8"
4
-
3
+ __version__ = "0.1.10"
@@ -2,39 +2,35 @@
2
2
  CLI entry point for dtlpymcp.
3
3
  Reads from STDIN and writes to STDOUT.
4
4
  """
5
+
5
6
  import sys
6
7
  import argparse
7
8
  from dtlpymcp.proxy import main as proxy_main
8
9
 
10
+
9
11
  def main():
10
- parser = argparse.ArgumentParser(
11
- description="Dataloop MCP Proxy Server CLI"
12
- )
12
+ parser = argparse.ArgumentParser(description="Dataloop MCP Proxy Server CLI")
13
13
  subparsers = parser.add_subparsers(dest="command", required=False)
14
14
 
15
15
  # 'start' subcommand
16
16
  start_parser = subparsers.add_parser("start", help="Start the MCP proxy server (STDIO mode)")
17
17
  start_parser.add_argument(
18
- "--sources-file",
19
- "-s",
20
- type=str,
21
- default=None,
22
- help="Path to a JSON file with MCP sources to load"
18
+ "--sources-file", "-s", type=str, default=None, help="Path to a JSON file with MCP sources to load"
23
19
  )
24
20
  start_parser.add_argument(
25
21
  "--init-timeout",
26
22
  "-t",
27
23
  type=float,
28
24
  default=30.0,
29
- help="Timeout in seconds for Dataloop context initialization (default: 30.0)"
25
+ help="Timeout in seconds for Dataloop context initialization (default: 30.0)",
30
26
  )
31
27
  start_parser.add_argument(
32
28
  "--log-level",
33
29
  "-l",
34
30
  type=str,
31
+ default="INFO",
35
32
  choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
36
- default="DEBUG",
37
- help="Logging verbosity level (default: DEBUG)"
33
+ help="Logging level (default: INFO)",
38
34
  )
39
35
 
40
36
  args = parser.parse_args()
@@ -45,5 +41,6 @@ def main():
45
41
  parser.print_help()
46
42
  return 1
47
43
 
44
+
48
45
  if __name__ == "__main__":
49
- main()
46
+ main()
@@ -1,32 +1,19 @@
1
-
2
1
  from mcp.server.fastmcp import FastMCP, Context
3
- from typing import Any, Optional
2
+ from typing import Any
4
3
  import traceback
5
4
  import os
6
-
5
+ import logging
6
+ from mcp.server.fastmcp.tools.base import Tool, FuncMetadata
7
+ from pydantic import create_model
8
+ from mcp.server.fastmcp.utilities.func_metadata import ArgModelBase
7
9
  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
10
 
11
- # Get the main logger
12
- logger = get_logger()
11
+ logger = logging.getLogger(__name__)
13
12
 
14
13
 
15
- def create_dataloop_mcp_server(sources_file: Optional[str] = None, init_timeout: float = 30.0) -> FastMCP:
14
+ def create_dataloop_mcp_server() -> FastMCP:
16
15
  """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")
16
+
30
17
  async def test(ctx: Context, ping: Any = None) -> dict[str, Any]:
31
18
  """Health check tool. Returns status ok and echoes ping if provided."""
32
19
  result = {"status": "ok"}
@@ -34,29 +21,43 @@ def create_dataloop_mcp_server(sources_file: Optional[str] = None, init_timeout:
34
21
  result["ping"] = ping
35
22
  return result
36
23
 
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
-
24
+ app = FastMCP(
25
+ name="Dataloop MCP Server",
26
+ instructions="A multi-tenant MCP server for Dataloop with authentication",
27
+ debug=True,
28
+ log_level="DEBUG",
29
+ )
30
+ tool_name = "test"
31
+ input_schema = {"properties": {"ping": {"type": "string", "default": "pong"}}, "required": ["ping"]}
32
+ # Create Dataloop context
33
+ dynamic_pydantic_model_params = DataloopContext.build_pydantic_fields_from_schema(input_schema)
34
+ arguments_model = create_model(f"{tool_name}Arguments", **dynamic_pydantic_model_params, __base__=ArgModelBase)
35
+ resp = FuncMetadata(arg_model=arguments_model)
36
+
37
+ app._tool_manager._tools[tool_name] = Tool(
38
+ fn=test,
39
+ name=tool_name,
40
+ description="Test tool for health checks",
41
+ parameters=input_schema,
42
+ is_async=True,
43
+ context_kwarg="ctx",
44
+ fn_metadata=resp,
45
+ annotations=None,
46
+ )
47
+
44
48
  return app
45
49
 
46
50
 
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
+ def main() -> int:
51
52
  logger.info("Starting Dataloop MCP server in stdio mode")
52
-
53
+
53
54
  # Validate environment variables
54
55
  if not os.environ.get('DATALOOP_API_KEY'):
55
56
  logger.error("DATALOOP_API_KEY environment variable is required")
56
57
  return 1
57
-
58
+
58
59
  try:
59
- mcp_server = create_dataloop_mcp_server(sources_file=sources_file, init_timeout=init_timeout)
60
+ mcp_server = create_dataloop_mcp_server()
60
61
  logger.info("Dataloop MCP server created successfully")
61
62
  logger.info("Starting server in stdio mode...")
62
63
  mcp_server.run(transport="stdio")
@@ -0,0 +1,174 @@
1
+ from mcp.server.fastmcp import FastMCP, Context
2
+ from mcp.server.fastmcp.tools.base import Tool, FuncMetadata
3
+ from typing import Any, Optional, List
4
+ import traceback
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ import os
8
+ import asyncio
9
+ import logging
10
+ from pydantic import create_model
11
+ from mcp.server.fastmcp.utilities.func_metadata import ArgModelBase
12
+
13
+ from dtlpymcp.utils.dtlpy_context import DataloopContext
14
+
15
+
16
+ def setup_logging(log_level: str = "INFO") -> logging.Logger:
17
+ """
18
+ Setup logging configuration with the specified log level.
19
+
20
+ Args:
21
+ log_level: Logging level as string (DEBUG, INFO, WARNING, ERROR, CRITICAL)
22
+
23
+ Returns:
24
+ logging.Logger: Configured logger instance
25
+ """
26
+ # Setup logging directory and file
27
+ log_dir = Path.home() / ".dataloop" / "mcplogs"
28
+ log_dir.mkdir(parents=True, exist_ok=True)
29
+ log_file = log_dir / f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log"
30
+
31
+ # Remove any existing handlers
32
+ for handler in logging.root.handlers[:]:
33
+ logging.root.removeHandler(handler)
34
+
35
+ # File handler with timestamp
36
+ file_handler = logging.FileHandler(log_file, mode="a", encoding="utf-8")
37
+ file_handler.setFormatter(
38
+ logging.Formatter(fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
39
+ )
40
+
41
+ # Console handler (default format)
42
+ console_handler = logging.StreamHandler()
43
+ console_handler.setFormatter(logging.Formatter(fmt="[%(levelname)s] %(name)s: %(message)s"))
44
+
45
+ # Convert string log level to logging constant
46
+ numeric_level = getattr(logging, log_level.upper(), logging.INFO)
47
+
48
+ # Configure root logger
49
+ logging.basicConfig(level=numeric_level, handlers=[file_handler, console_handler])
50
+
51
+ # Get the main logger
52
+ logger = logging.getLogger("dtlpymcp")
53
+ logger.info(f"Logging configured with level: {log_level.upper()}")
54
+ logger.info(f"Log file: {log_file}")
55
+
56
+ return logger
57
+
58
+
59
+ def run_async(coro):
60
+ try:
61
+ loop = asyncio.get_running_loop()
62
+ except RuntimeError:
63
+ # No event loop running
64
+ return asyncio.run(coro)
65
+ else:
66
+ # Already running event loop
67
+ return loop.create_task(coro)
68
+
69
+
70
+ async def initialize_dataloop_context(sources_file: Optional[str] = None, init_timeout: float = 30.0) -> List[Tool]:
71
+ """
72
+ Initialize Dataloop context with timeout protection.
73
+
74
+ Args:
75
+ sources_file: Path to sources file
76
+ init_timeout: Timeout in seconds for initialization
77
+
78
+ Returns:
79
+ List[Tool]: List of tools
80
+ """
81
+ logger = logging.getLogger("dtlpymcp")
82
+ try:
83
+ tools = []
84
+ dl_context = DataloopContext(
85
+ token=os.environ.get('DATALOOP_API_KEY'),
86
+ env=os.environ.get('DATALOOP_ENV', 'prod'),
87
+ sources_file=sources_file,
88
+ )
89
+ logger.info("Initializing Dataloop context...")
90
+ await dl_context.initialize()
91
+ logger.info("Dataloop context initialized successfully")
92
+
93
+ logger.info(f"Adding tools from {len(dl_context.mcp_sources)} sources")
94
+ for source in dl_context.mcp_sources:
95
+ logger.info(f"Adding tools from source: {source.dpk_name}")
96
+ for tool in source.tools:
97
+ tools.append(tool)
98
+ logger.info(f"Registered tool: {tool.name}")
99
+
100
+ return tools
101
+
102
+ except asyncio.TimeoutError:
103
+ logger.error("Timeout during Dataloop context initialization")
104
+ return []
105
+ except Exception as e:
106
+ logger.error(f"Failed to initialize Dataloop context: {e}")
107
+ return []
108
+
109
+
110
+ def create_dataloop_mcp_server(sources_file: Optional[str] = None, init_timeout: float = 30.0) -> FastMCP:
111
+ """Create a FastMCP server for Dataloop with Bearer token authentication."""
112
+
113
+ async def test(ctx: Context, ping: Any = None) -> dict[str, Any]:
114
+ """Health check tool. Returns status ok and echoes ping if provided."""
115
+ result = {"status": "ok"}
116
+ if ping is not None:
117
+ result["ping"] = ping
118
+ return result
119
+
120
+ tool_name = "test"
121
+ input_schema = {"properties": {"ping": {"type": "string"}}, "required": ["ping"]}
122
+ dynamic_pydantic_model_params = DataloopContext.build_pydantic_fields_from_schema(input_schema)
123
+ arguments_model = create_model(f"{tool_name}Arguments", **dynamic_pydantic_model_params, __base__=ArgModelBase)
124
+ resp = FuncMetadata(arg_model=arguments_model)
125
+ t = Tool(
126
+ fn=test,
127
+ name=tool_name,
128
+ description="Test tool for health checks",
129
+ parameters=input_schema,
130
+ is_async=True,
131
+ context_kwarg="ctx",
132
+ fn_metadata=resp,
133
+ annotations=None,
134
+ )
135
+ tools = [t]
136
+ tools.extend(run_async(initialize_dataloop_context(sources_file=sources_file, init_timeout=init_timeout)))
137
+ app = FastMCP(
138
+ name="Dataloop MCP Server",
139
+ instructions="A multi-tenant MCP server for Dataloop with authentication",
140
+ debug=True,
141
+ tools=tools,
142
+ )
143
+
144
+ return app
145
+
146
+
147
+ def main(sources_file: Optional[str] = None, init_timeout: float = 30.0, log_level: str = "INFO") -> int:
148
+ # Setup logging with the specified level
149
+ logger = setup_logging(log_level)
150
+
151
+ logger.info("Starting Dataloop MCP server in stdio mode")
152
+
153
+ # Validate environment variables
154
+ if not os.environ.get('DATALOOP_API_KEY'):
155
+ logger.error("DATALOOP_API_KEY environment variable is required")
156
+ return 1
157
+
158
+ try:
159
+ mcp_server = create_dataloop_mcp_server(sources_file=sources_file, init_timeout=init_timeout)
160
+ logger.info("Dataloop MCP server created successfully")
161
+ logger.info("Starting server in stdio mode...")
162
+ mcp_server.run(transport="stdio")
163
+ return 0
164
+ except KeyboardInterrupt:
165
+ logger.info("Server stopped by user")
166
+ return 0
167
+ except Exception as e:
168
+ logger.error(f"Failed to start MCP server: {e}")
169
+ logger.error(f"Traceback: {traceback.format_exc()}")
170
+ return 1
171
+
172
+
173
+ if __name__ == "__main__":
174
+ main()
@@ -9,13 +9,12 @@ from mcp import ClientSession
9
9
  import dtlpy as dl
10
10
  import traceback
11
11
  import requests
12
+ import logging
12
13
  import time
13
14
  import jwt
14
15
  import json
15
16
 
16
- from dtlpymcp.utils.logging_config import get_logger
17
-
18
- logger = get_logger("dtlpy_context")
17
+ logger = logging.getLogger("dtlpymcp")
19
18
 
20
19
 
21
20
  class MCPSource(BaseModel):
@@ -52,7 +51,8 @@ class DataloopContext:
52
51
  filters = dl.Filters(resource='apps')
53
52
  filters.add(field="dpkName", values="dataloop-mcp*")
54
53
  filters.add(field="scope", values="system")
55
- apps = list(dl.apps.list(filters=filters).all())
54
+ # IMPORTANT: Listing with `all()` cause everything to get stuck. getting only first page using `items` for now
55
+ apps = dl.apps.list(filters=filters).items
56
56
  if len(apps) == 0:
57
57
  raise ValueError(f"No app found for DPK name: dataloop-mcp*")
58
58
  sources = []
@@ -230,17 +230,19 @@ class DataloopContext:
230
230
  logger.info(f"Discovered {len(tools.tools)} tools for source {source.dpk_name}")
231
231
  return (source.dpk_name, tools, call_fn)
232
232
 
233
- def openapi_type_to_python(self, type_str):
233
+ @staticmethod
234
+ def openapi_type_to_python(type_str):
234
235
  return {"string": str, "integer": int, "number": float, "boolean": bool, "array": list, "object": dict}.get(
235
236
  type_str, str
236
237
  )
237
238
 
238
- def build_pydantic_fields_from_schema(self, input_schema):
239
+ @staticmethod
240
+ def build_pydantic_fields_from_schema(input_schema):
239
241
  required = set(input_schema.get("required", []))
240
242
  properties = input_schema.get("properties", {})
241
243
  fields = {}
242
244
  for name, prop in properties.items():
243
- py_type = self.openapi_type_to_python(prop.get("type", "string"))
245
+ py_type = DataloopContext.openapi_type_to_python(prop.get("type", "string"))
244
246
  if name in required:
245
247
  fields[name] = (py_type, Field(...))
246
248
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtlpymcp
3
- Version: 0.1.8
3
+ Version: 0.1.10
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
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  dtlpymcp/__init__.py
5
5
  dtlpymcp/__main__.py
6
+ dtlpymcp/min_proxy.py
6
7
  dtlpymcp/proxy.py
7
8
  dtlpymcp.egg-info/PKG-INFO
8
9
  dtlpymcp.egg-info/SOURCES.txt
@@ -11,8 +12,6 @@ dtlpymcp.egg-info/entry_points.txt
11
12
  dtlpymcp.egg-info/requires.txt
12
13
  dtlpymcp.egg-info/top_level.txt
13
14
  dtlpymcp/utils/dtlpy_context.py
14
- dtlpymcp/utils/logging_config.py
15
- dtlpymcp/utils/server_utils.py
16
15
  tests/test_context.py
17
16
  tests/test_custom_sources_file.py
18
17
  tests/test_list_platform_tools.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dtlpymcp"
7
- version = "0.1.8"
7
+ version = "0.1.10"
8
8
  description = "STDIO MCP proxy server for Dataloop platform."
9
9
  authors = [
10
10
  { name = "Your Name", email = "your.email@example.com" }
@@ -1,32 +1,34 @@
1
1
  import asyncio
2
2
  import random
3
3
  import json
4
+ import sys
4
5
  import os
5
6
  from datetime import timedelta
6
7
  from mcp.client.stdio import stdio_client
7
8
  from mcp import ClientSession, StdioServerParameters
8
9
  import dtlpy as dl
9
10
 
10
- dl.setenv('rc')
11
+ dl.setenv('prod')
11
12
  if dl.token_expired():
12
13
  dl.login()
13
14
  token = dl.token()
14
15
  env = {"DATALOOP_API_KEY": str(token)} if token else None
15
16
  # Create server parameters for stdio connection
16
17
  server_params = StdioServerParameters(
17
- command="dtlpymcp", # Executable
18
- args=["start"], # Command line arguments
18
+ command=sys.executable, # Use current Python interpreter
19
+ args=["-m", "dtlpymcp.min_proxy"], # Run as module with start command
19
20
  env=env, # Optional environment variables
20
21
  cwd=os.getcwd(),
21
22
  )
22
23
 
23
24
 
25
+
24
26
  async def test_health_check():
25
27
  print("[TEST CLIENT] Connecting to MCP server and calling test tool...")
26
28
  async with stdio_client(server=server_params) as (read, write):
27
29
  async with ClientSession(read, write, read_timeout_seconds=timedelta(seconds=60)) as session:
28
30
  await session.initialize()
29
- num = random.randint(1, 1000000)
31
+ num = str(random.randint(1, 1000000))
30
32
  tool_result = await session.call_tool("test", {"ping": num})
31
33
  print("[RESULT]", tool_result)
32
34
  assert json.loads(tool_result.content[0].text).get("status") == "ok", "Health check failed!"
@@ -34,6 +36,6 @@ async def test_health_check():
34
36
 
35
37
 
36
38
  if __name__ == "__main__":
39
+
37
40
 
38
41
  asyncio.run(test_health_check())
39
- # asyncio.run(test_ask_dataloop())
@@ -1,61 +0,0 @@
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}")
@@ -1,91 +0,0 @@
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
File without changes
File without changes
File without changes
File without changes