dtlpymcp 0.1.9__py3-none-any.whl → 0.1.11__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 -32
- dtlpymcp/__main__.py +9 -1
- dtlpymcp/min_proxy.py +1 -1
- dtlpymcp/proxy.py +51 -6
- dtlpymcp/utils/dtlpy_context.py +34 -0
- {dtlpymcp-0.1.9.dist-info → dtlpymcp-0.1.11.dist-info}/METADATA +1 -1
- dtlpymcp-0.1.11.dist-info/RECORD +10 -0
- dtlpymcp-0.1.9.dist-info/RECORD +0 -10
- {dtlpymcp-0.1.9.dist-info → dtlpymcp-0.1.11.dist-info}/WHEEL +0 -0
- {dtlpymcp-0.1.9.dist-info → dtlpymcp-0.1.11.dist-info}/entry_points.txt +0 -0
- {dtlpymcp-0.1.9.dist-info → dtlpymcp-0.1.11.dist-info}/top_level.txt +0 -0
dtlpymcp/__init__.py
CHANGED
|
@@ -1,34 +1,3 @@
|
|
|
1
1
|
from .utils.dtlpy_context import DataloopContext, MCPSource
|
|
2
|
-
import logging
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from pathlib import Path
|
|
5
2
|
|
|
6
|
-
__version__ = "0.1.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Setup logging directory and file
|
|
10
|
-
log_dir = Path.home() / ".dataloop" / "mcplogs"
|
|
11
|
-
log_dir.mkdir(parents=True, exist_ok=True)
|
|
12
|
-
log_file = log_dir / f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log"
|
|
13
|
-
|
|
14
|
-
# Remove any existing handlers
|
|
15
|
-
for handler in logging.root.handlers[:]:
|
|
16
|
-
logging.root.removeHandler(handler)
|
|
17
|
-
|
|
18
|
-
# File handler with timestamp
|
|
19
|
-
file_handler = logging.FileHandler(log_file, mode="a", encoding="utf-8")
|
|
20
|
-
file_handler.setFormatter(
|
|
21
|
-
logging.Formatter(fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
# Console handler (default format)
|
|
25
|
-
console_handler = logging.StreamHandler()
|
|
26
|
-
console_handler.setFormatter(logging.Formatter(fmt="[%(levelname)s] %(name)s: %(message)s"))
|
|
27
|
-
|
|
28
|
-
# Configure root logger
|
|
29
|
-
logging.basicConfig(level=logging.DEBUG, handlers=[file_handler, console_handler])
|
|
30
|
-
|
|
31
|
-
# Get the main logger
|
|
32
|
-
logger = logging.getLogger("dtlpymcp")
|
|
33
|
-
logger.info(f"Logging configured with level: DEBUG")
|
|
34
|
-
logger.info(f"Log file: {log_file}")
|
|
3
|
+
__version__ = "0.1.11"
|
dtlpymcp/__main__.py
CHANGED
|
@@ -24,11 +24,19 @@ def main():
|
|
|
24
24
|
default=30.0,
|
|
25
25
|
help="Timeout in seconds for Dataloop context initialization (default: 30.0)",
|
|
26
26
|
)
|
|
27
|
+
start_parser.add_argument(
|
|
28
|
+
"--log-level",
|
|
29
|
+
"-l",
|
|
30
|
+
type=str,
|
|
31
|
+
default="INFO",
|
|
32
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
33
|
+
help="Logging level (default: INFO)",
|
|
34
|
+
)
|
|
27
35
|
|
|
28
36
|
args = parser.parse_args()
|
|
29
37
|
|
|
30
38
|
if args.command == "start":
|
|
31
|
-
sys.exit(proxy_main(sources_file=args.sources_file, init_timeout=args.init_timeout))
|
|
39
|
+
sys.exit(proxy_main(sources_file=args.sources_file, init_timeout=args.init_timeout, log_level=args.log_level))
|
|
32
40
|
else:
|
|
33
41
|
parser.print_help()
|
|
34
42
|
return 1
|
dtlpymcp/min_proxy.py
CHANGED
|
@@ -28,7 +28,7 @@ def create_dataloop_mcp_server() -> FastMCP:
|
|
|
28
28
|
log_level="DEBUG",
|
|
29
29
|
)
|
|
30
30
|
tool_name = "test"
|
|
31
|
-
input_schema = {"properties": {"ping": {"type": "string", "default": "pong"}}, "required": ["ping"]}
|
|
31
|
+
input_schema = {"type": "object", "properties": {"ping": {"type": "string", "default": "pong"}}, "required": ["ping"]}
|
|
32
32
|
# Create Dataloop context
|
|
33
33
|
dynamic_pydantic_model_params = DataloopContext.build_pydantic_fields_from_schema(input_schema)
|
|
34
34
|
arguments_model = create_model(f"{tool_name}Arguments", **dynamic_pydantic_model_params, __base__=ArgModelBase)
|
dtlpymcp/proxy.py
CHANGED
|
@@ -2,6 +2,8 @@ from mcp.server.fastmcp import FastMCP, Context
|
|
|
2
2
|
from mcp.server.fastmcp.tools.base import Tool, FuncMetadata
|
|
3
3
|
from typing import Any, Optional, List
|
|
4
4
|
import traceback
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
import os
|
|
6
8
|
import asyncio
|
|
7
9
|
import logging
|
|
@@ -10,7 +12,48 @@ from mcp.server.fastmcp.utilities.func_metadata import ArgModelBase
|
|
|
10
12
|
|
|
11
13
|
from dtlpymcp.utils.dtlpy_context import DataloopContext
|
|
12
14
|
|
|
13
|
-
|
|
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
|
|
14
57
|
|
|
15
58
|
|
|
16
59
|
def run_async(coro):
|
|
@@ -29,13 +72,13 @@ async def initialize_dataloop_context(sources_file: Optional[str] = None, init_t
|
|
|
29
72
|
Initialize Dataloop context with timeout protection.
|
|
30
73
|
|
|
31
74
|
Args:
|
|
32
|
-
|
|
33
|
-
app: The FastMCP app instance to register tools with
|
|
75
|
+
sources_file: Path to sources file
|
|
34
76
|
init_timeout: Timeout in seconds for initialization
|
|
35
77
|
|
|
36
78
|
Returns:
|
|
37
79
|
List[Tool]: List of tools
|
|
38
80
|
"""
|
|
81
|
+
logger = logging.getLogger("dtlpymcp")
|
|
39
82
|
try:
|
|
40
83
|
tools = []
|
|
41
84
|
dl_context = DataloopContext(
|
|
@@ -75,7 +118,7 @@ def create_dataloop_mcp_server(sources_file: Optional[str] = None, init_timeout:
|
|
|
75
118
|
return result
|
|
76
119
|
|
|
77
120
|
tool_name = "test"
|
|
78
|
-
input_schema = {"properties": {"ping": {"type": "string"}}, "required": ["ping"]}
|
|
121
|
+
input_schema = {"type": "object", "properties": {"ping": {"type": "string"}}, "required": ["ping"]}
|
|
79
122
|
dynamic_pydantic_model_params = DataloopContext.build_pydantic_fields_from_schema(input_schema)
|
|
80
123
|
arguments_model = create_model(f"{tool_name}Arguments", **dynamic_pydantic_model_params, __base__=ArgModelBase)
|
|
81
124
|
resp = FuncMetadata(arg_model=arguments_model)
|
|
@@ -101,8 +144,10 @@ def create_dataloop_mcp_server(sources_file: Optional[str] = None, init_timeout:
|
|
|
101
144
|
return app
|
|
102
145
|
|
|
103
146
|
|
|
104
|
-
def main(sources_file: Optional[str] = None, init_timeout: float = 30.0) -> int:
|
|
105
|
-
|
|
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
|
+
|
|
106
151
|
logger.info("Starting Dataloop MCP server in stdio mode")
|
|
107
152
|
|
|
108
153
|
# Validate environment variables
|
dtlpymcp/utils/dtlpy_context.py
CHANGED
|
@@ -95,6 +95,10 @@ class DataloopContext:
|
|
|
95
95
|
ns_tool_name = f"{server_name}.{tool_name}"
|
|
96
96
|
description = tool.description
|
|
97
97
|
input_schema = tool.inputSchema
|
|
98
|
+
|
|
99
|
+
# Normalize input schema to ensure it has "type": "object" at root level
|
|
100
|
+
# This is required by the MCP specification
|
|
101
|
+
input_schema = self.normalize_input_schema(input_schema)
|
|
98
102
|
|
|
99
103
|
def build_handler(tool_name):
|
|
100
104
|
async def inner(**kwargs):
|
|
@@ -230,6 +234,36 @@ class DataloopContext:
|
|
|
230
234
|
logger.info(f"Discovered {len(tools.tools)} tools for source {source.dpk_name}")
|
|
231
235
|
return (source.dpk_name, tools, call_fn)
|
|
232
236
|
|
|
237
|
+
@staticmethod
|
|
238
|
+
def normalize_input_schema(input_schema: dict) -> dict:
|
|
239
|
+
"""
|
|
240
|
+
Normalize input schema to ensure it conforms to MCP specification.
|
|
241
|
+
The schema must have "type": "object" at the root level.
|
|
242
|
+
"""
|
|
243
|
+
if not isinstance(input_schema, dict):
|
|
244
|
+
return {"type": "object", "properties": {}, "required": []}
|
|
245
|
+
|
|
246
|
+
# Create a copy to avoid mutating the original
|
|
247
|
+
normalized = input_schema.copy()
|
|
248
|
+
|
|
249
|
+
# Ensure type is "object" at root level
|
|
250
|
+
if "type" not in normalized:
|
|
251
|
+
normalized["type"] = "object"
|
|
252
|
+
elif normalized.get("type") != "object":
|
|
253
|
+
# If type exists but isn't "object", log a warning and fix it
|
|
254
|
+
logger.warning(f"Input schema has type '{normalized.get('type')}' instead of 'object', fixing...")
|
|
255
|
+
normalized["type"] = "object"
|
|
256
|
+
|
|
257
|
+
# Ensure properties exist
|
|
258
|
+
if "properties" not in normalized:
|
|
259
|
+
normalized["properties"] = {}
|
|
260
|
+
|
|
261
|
+
# Ensure required exists (can be empty list)
|
|
262
|
+
if "required" not in normalized:
|
|
263
|
+
normalized["required"] = []
|
|
264
|
+
|
|
265
|
+
return normalized
|
|
266
|
+
|
|
233
267
|
@staticmethod
|
|
234
268
|
def openapi_type_to_python(type_str):
|
|
235
269
|
return {"string": str, "integer": int, "number": float, "boolean": bool, "array": list, "object": dict}.get(
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
dtlpymcp/__init__.py,sha256=-iPfWD7k43HTz0akAm7BHONlJipmtYiMjmF1CCEj0us,87
|
|
2
|
+
dtlpymcp/__main__.py,sha256=ZsXN8guga8Qo-94bSvgC6u9s5gmmdyppUijb-_bCxCw,1347
|
|
3
|
+
dtlpymcp/min_proxy.py,sha256=QYGoqclGNN7ZMjoLzWBLCxUm4yT2xCoLTbtdFRsqlEk,2572
|
|
4
|
+
dtlpymcp/proxy.py,sha256=IzZrK6s37sXM-AM2L2m_K9AveT7uX_VSxwgrwggJYV4,6213
|
|
5
|
+
dtlpymcp/utils/dtlpy_context.py,sha256=6ad136vmk0Cstp10cAcY6YaZkUTgFzyvMlocIP6DwiE,12112
|
|
6
|
+
dtlpymcp-0.1.11.dist-info/METADATA,sha256=G895DOpzSG2bf9EzmzGIIO2MJgH0RtX6W48Z_eXqnRU,2190
|
|
7
|
+
dtlpymcp-0.1.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
dtlpymcp-0.1.11.dist-info/entry_points.txt,sha256=6hRVZNTjQevj7erwt9dAOURtPVrSrYu6uHXhAlhTaXQ,52
|
|
9
|
+
dtlpymcp-0.1.11.dist-info/top_level.txt,sha256=z85v20pIEnY3cBaWgwhU3EZS4WAZRywejhIutwd-iHk,9
|
|
10
|
+
dtlpymcp-0.1.11.dist-info/RECORD,,
|
dtlpymcp-0.1.9.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
dtlpymcp/__init__.py,sha256=lhPPMskLqsH9l-ucRa0Zj2oCQEhsaoT1xtZX67kTwLQ,1178
|
|
2
|
-
dtlpymcp/__main__.py,sha256=RWyPaVtfYpLyJplQpQ8ZcGfP5UTfbefQMo5MB9HD9CY,1084
|
|
3
|
-
dtlpymcp/min_proxy.py,sha256=n9KzWwaGU-8uUKRJigGHAnyZ6GXfNl5bwMpULfBrbEc,2554
|
|
4
|
-
dtlpymcp/proxy.py,sha256=0xMlO7QUUk4Kq6pb4F0b-n_c0ZWjcFY1jJHqkMwGxAQ,4515
|
|
5
|
-
dtlpymcp/utils/dtlpy_context.py,sha256=qeKwptMx_fenYTRs5-l3711yPyo2iof_FoQX8jM3fmE,10670
|
|
6
|
-
dtlpymcp-0.1.9.dist-info/METADATA,sha256=YBf64o9pxbX9Shb2zxzoN9JWZk6prSJqGu0EccW4s6Y,2189
|
|
7
|
-
dtlpymcp-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
dtlpymcp-0.1.9.dist-info/entry_points.txt,sha256=6hRVZNTjQevj7erwt9dAOURtPVrSrYu6uHXhAlhTaXQ,52
|
|
9
|
-
dtlpymcp-0.1.9.dist-info/top_level.txt,sha256=z85v20pIEnY3cBaWgwhU3EZS4WAZRywejhIutwd-iHk,9
|
|
10
|
-
dtlpymcp-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|