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 +1 -1
- dtlpymcp/proxy.py +101 -94
- dtlpymcp/utils/dtlpy_context.py +4 -2
- {dtlpymcp-0.1.3.dist-info → dtlpymcp-0.1.4.dist-info}/METADATA +4 -2
- dtlpymcp-0.1.4.dist-info/RECORD +10 -0
- dtlpymcp-0.1.3.dist-info/RECORD +0 -10
- {dtlpymcp-0.1.3.dist-info → dtlpymcp-0.1.4.dist-info}/WHEEL +0 -0
- {dtlpymcp-0.1.3.dist-info → dtlpymcp-0.1.4.dist-info}/entry_points.txt +0 -0
- {dtlpymcp-0.1.3.dist-info → dtlpymcp-0.1.4.dist-info}/top_level.txt +0 -0
dtlpymcp/__init__.py
CHANGED
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
|
8
|
-
import os
|
9
|
-
from pathlib import Path
|
10
|
-
|
11
|
-
from . import SOURCES_FILEPATH
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
log_dir =
|
16
|
-
log_dir.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
file_handler
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
console_handler
|
31
|
-
|
32
|
-
|
33
|
-
logging.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
)
|
54
|
-
|
55
|
-
sources_file=sources_file)
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
logger.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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()
|
dtlpymcp/utils/dtlpy_context.py
CHANGED
@@ -15,7 +15,7 @@ import asyncio
|
|
15
15
|
import time
|
16
16
|
import jwt
|
17
17
|
import json
|
18
|
-
from
|
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
|
+
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": "
|
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,,
|
dtlpymcp-0.1.3.dist-info/RECORD
DELETED
@@ -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,,
|
File without changes
|
File without changes
|
File without changes
|