dtlpymcp 0.1.3__py3-none-any.whl → 0.1.4__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
@@ -3,5 +3,5 @@ SOURCES_FILEPATH = os.path.join(os.path.dirname(__file__), "default_sources.json
3
3
 
4
4
  from .utils.dtlpy_context import DataloopContext, MCPSource
5
5
 
6
- __version__ = "0.1.3"
6
+ __version__ = "0.1.4"
7
7
 
dtlpymcp/proxy.py CHANGED
@@ -1,94 +1,101 @@
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 json
8
- import os
9
- from pathlib import Path
10
-
11
- from . import SOURCES_FILEPATH
12
- from .utils.dtlpy_context import DataloopContext, MCPSource
13
-
14
- # Setup logging to both console and file with timestamp
15
- log_dir = Path.home() / ".dataloop" / "mcplogs"
16
- log_dir.mkdir(parents=True, exist_ok=True)
17
- log_file = log_dir / f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log"
18
-
19
- # Remove any existing handlers
20
- for handler in logging.root.handlers[:]:
21
- logging.root.removeHandler(handler)
22
-
23
- # File handler with timestamp
24
- file_handler = logging.FileHandler(log_file, mode="a", encoding="utf-8")
25
- file_handler.setFormatter(
26
- logging.Formatter(fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
27
- )
28
-
29
- # Console handler (default format)
30
- console_handler = logging.StreamHandler()
31
- console_handler.setFormatter(logging.Formatter(fmt="[%(levelname)s] %(name)s: %(message)s"))
32
-
33
- logging.basicConfig(level=logging.INFO, handlers=[file_handler, console_handler])
34
- logger = logging.getLogger("[DATALOOP-MCP]")
35
-
36
-
37
- class ServerSettings(BaseSettings):
38
- """Settings for the Dataloop MCP server."""
39
-
40
- model_config = SettingsConfigDict(env_prefix="MCP_DATALOOP_")
41
-
42
- def __init__(self, **data):
43
- super().__init__(**data)
44
-
45
-
46
- def create_dataloop_mcp_server(settings: ServerSettings, sources_file: str) -> FastMCP:
47
- """Create a FastMCP server for Dataloop with Bearer token authentication."""
48
- app = FastMCP(
49
- name="Dataloop MCP Server",
50
- instructions="A multi-tenant MCP server for Dataloop with authentication",
51
- stateless_http=True,
52
- debug=True,
53
- )
54
- dl_context = DataloopContext(token=os.environ.get('DATALOOP_API_KEY'),
55
- sources_file=sources_file)
56
-
57
- @app.tool(description="Test tool for health checks")
58
- async def test(ctx: Context, ping: Any = None) -> dict[str, Any]:
59
- """Health check tool. Returns status ok and echoes ping if provided."""
60
- result = {"status": "ok"}
61
- if ping is not None:
62
- result["ping"] = ping
63
- return result
64
-
65
- for source in dl_context.mcp_sources:
66
- for tool in source.tools:
67
- app._tool_manager._tools[tool.name] = tool
68
- return app
69
-
70
-
71
- def main(sources_file: Optional[str] = None) -> int:
72
- logger.info("Starting Dataloop MCP server in stdio mode")
73
- try:
74
- settings = ServerSettings()
75
- logger.info("Successfully configured Dataloop MCP server")
76
- except Exception as e:
77
- logger.error(f"Unexpected error during startup:\n{e}")
78
- return 1
79
- try:
80
- if sources_file is None:
81
- sources_file = SOURCES_FILEPATH
82
- logger.info(f"Using sources file: {sources_file}")
83
- mcp_server = create_dataloop_mcp_server(settings, sources_file)
84
- logger.info("Starting Dataloop MCP server in stdio mode")
85
- logger.info("Users should provide their API key in the Authorization header as a Bearer token")
86
- mcp_server.run(transport="stdio")
87
- return 0
88
- except Exception:
89
- logger.error(f"Failed to start MCP server: {traceback.format_exc()}")
90
- return 1
91
-
92
-
93
- if __name__ == "__main__":
94
- main()
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()
@@ -15,7 +15,7 @@ import asyncio
15
15
  import time
16
16
  import jwt
17
17
  import json
18
- from .. import SOURCES_FILEPATH
18
+ from dtlpymcp import SOURCES_FILEPATH
19
19
 
20
20
  # Utility to run async code from sync or async context
21
21
  # If called from a running event loop, returns a Task (caller must handle it)
@@ -48,8 +48,9 @@ class DataloopContext:
48
48
  Handles JWTs, server URLs, and dynamic tool registration for multi-tenant environments.
49
49
  """
50
50
 
51
- def __init__(self, token: str = None, sources_file: str = None):
51
+ def __init__(self, token: str = None, sources_file: str = None, env: str = 'prod'):
52
52
  self._token = token
53
+ self.env = env
53
54
  self.mcp_sources: List[MCPSource] = []
54
55
  logger.info("DataloopContext initialized.")
55
56
  if sources_file is None:
@@ -126,6 +127,7 @@ class DataloopContext:
126
127
  Get the source URL and app JWT for a given DPK name using Dataloop SDK.
127
128
  """
128
129
  try:
130
+ dl.setenv(self.env)
129
131
  dl.client_api.token = self.token
130
132
  dpk = dl.dpks.get(dpk_name=source.dpk_name)
131
133
  apps_filters = dl.Filters(field='dpkName', values=dpk.name, resource='apps')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtlpymcp
3
- Version: 0.1.3
3
+ Version: 0.1.4
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
@@ -68,8 +68,10 @@ To add this MCP to Cursor, add the following to your configuration:
68
68
  {
69
69
  "mcpServers": {
70
70
  "dataloop-ai-mcp": {
71
- "command": "dtlpymcp start",
71
+ "command": "uvx",
72
+ "args": ["dtlpymcp", "start"],
72
73
  "env": {
74
+ "DATALOOP_ENV": "prod",
73
75
  "DATALOOP_API_KEY": "API KEY"
74
76
  }
75
77
  }
@@ -0,0 +1,10 @@
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,,
@@ -1,10 +0,0 @@
1
- dtlpymcp/__init__.py,sha256=ISimynz4Nggmf-PH4y1xR2gddnazpe77QNLxSAWM9IM,185
2
- dtlpymcp/__main__.py,sha256=1lo4qoZAoUHl9rkt1YGB2kCpKg5cIrTSBSrznxyk6F4,884
3
- dtlpymcp/default_sources.json,sha256=Es3XZcdMpe6o5FyOoqW9FG_oUjC5gkNbBC6N9eBqpQs,418
4
- dtlpymcp/proxy.py,sha256=_7BGShm-V5LZCe24vbFsysF-Z_Yc70Uuxilot3P5uP4,3447
5
- dtlpymcp/utils/dtlpy_context.py,sha256=A8TCGjcvRvUc38etrIY-IZlU-JDOPwVUsUJKweKJ1nk,9670
6
- dtlpymcp-0.1.3.dist-info/METADATA,sha256=zWm9OaXKvM9oIIphSuPNDgtWbylrLkdFlPMpFag3D28,1617
7
- dtlpymcp-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- dtlpymcp-0.1.3.dist-info/entry_points.txt,sha256=6hRVZNTjQevj7erwt9dAOURtPVrSrYu6uHXhAlhTaXQ,52
9
- dtlpymcp-0.1.3.dist-info/top_level.txt,sha256=z85v20pIEnY3cBaWgwhU3EZS4WAZRywejhIutwd-iHk,9
10
- dtlpymcp-0.1.3.dist-info/RECORD,,