universal-mcp 0.1.8rc2__py3-none-any.whl → 0.1.8rc3__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.
- universal_mcp/__init__.py +0 -2
- universal_mcp/analytics.py +75 -0
- universal_mcp/applications/application.py +27 -5
- universal_mcp/applications/calendly/app.py +413 -160
- universal_mcp/applications/coda/README.md +133 -0
- universal_mcp/applications/coda/__init__.py +0 -0
- universal_mcp/applications/coda/app.py +3704 -0
- universal_mcp/applications/e2b/app.py +6 -7
- universal_mcp/applications/firecrawl/app.py +1 -1
- universal_mcp/applications/github/app.py +41 -42
- universal_mcp/applications/google_calendar/app.py +20 -20
- universal_mcp/applications/google_docs/app.py +22 -29
- universal_mcp/applications/google_drive/app.py +53 -59
- universal_mcp/applications/google_mail/app.py +40 -40
- universal_mcp/applications/google_sheet/app.py +44 -51
- universal_mcp/applications/markitdown/app.py +4 -4
- universal_mcp/applications/notion/app.py +93 -83
- universal_mcp/applications/perplexity/app.py +5 -5
- universal_mcp/applications/reddit/app.py +32 -32
- universal_mcp/applications/resend/app.py +4 -4
- universal_mcp/applications/serpapi/app.py +4 -4
- universal_mcp/applications/tavily/app.py +4 -4
- universal_mcp/applications/wrike/app.py +566 -226
- universal_mcp/applications/youtube/app.py +626 -166
- universal_mcp/applications/zenquotes/app.py +3 -3
- universal_mcp/exceptions.py +1 -0
- universal_mcp/integrations/__init__.py +11 -2
- universal_mcp/integrations/integration.py +2 -2
- universal_mcp/logger.py +3 -56
- universal_mcp/servers/__init__.py +2 -1
- universal_mcp/servers/server.py +76 -77
- universal_mcp/stores/store.py +5 -3
- universal_mcp/tools/__init__.py +1 -1
- universal_mcp/tools/adapters.py +4 -1
- universal_mcp/tools/func_metadata.py +5 -6
- universal_mcp/tools/tools.py +108 -51
- universal_mcp/utils/docgen.py +121 -69
- universal_mcp/utils/docstring_parser.py +44 -21
- universal_mcp/utils/dump_app_tools.py +33 -23
- universal_mcp/utils/openapi.py +121 -47
- {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc3.dist-info}/METADATA +2 -2
- universal_mcp-0.1.8rc3.dist-info/RECORD +75 -0
- universal_mcp-0.1.8rc2.dist-info/RECORD +0 -71
- {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc3.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc3.dist-info}/entry_points.txt +0 -0
@@ -8,16 +8,16 @@ class ZenquotesApp(APIApplication):
|
|
8
8
|
def get_quote(self) -> str:
|
9
9
|
"""
|
10
10
|
Fetches a random inspirational quote from the Zen Quotes API.
|
11
|
-
|
11
|
+
|
12
12
|
Returns:
|
13
13
|
A formatted string containing the quote and its author in the format 'quote - author'
|
14
|
-
|
14
|
+
|
15
15
|
Raises:
|
16
16
|
RequestException: If the HTTP request to the Zen Quotes API fails
|
17
17
|
JSONDecodeError: If the API response contains invalid JSON
|
18
18
|
IndexError: If the API response doesn't contain any quotes
|
19
19
|
KeyError: If the quote data doesn't contain the expected 'q' or 'a' fields
|
20
|
-
|
20
|
+
|
21
21
|
Tags:
|
22
22
|
fetch, quotes, api, http, important
|
23
23
|
"""
|
universal_mcp/exceptions.py
CHANGED
@@ -8,7 +8,9 @@ from universal_mcp.integrations.integration import (
|
|
8
8
|
from universal_mcp.stores.store import BaseStore
|
9
9
|
|
10
10
|
|
11
|
-
def integration_from_config(
|
11
|
+
def integration_from_config(
|
12
|
+
config: IntegrationConfig, store: BaseStore | None = None, **kwargs
|
13
|
+
) -> Integration:
|
12
14
|
if config.type == "api_key":
|
13
15
|
return ApiKeyIntegration(config.name, store=store, **kwargs)
|
14
16
|
elif config.type == "agentr":
|
@@ -19,4 +21,11 @@ def integration_from_config(config: IntegrationConfig, store: BaseStore | None =
|
|
19
21
|
else:
|
20
22
|
raise ValueError(f"Unsupported integration type: {config.type}")
|
21
23
|
|
22
|
-
|
24
|
+
|
25
|
+
__all__ = [
|
26
|
+
"AgentRIntegration",
|
27
|
+
"Integration",
|
28
|
+
"ApiKeyIntegration",
|
29
|
+
"OAuthIntegration",
|
30
|
+
"integration_from_config",
|
31
|
+
]
|
@@ -106,9 +106,9 @@ class ApiKeyIntegration(Integration):
|
|
106
106
|
"""
|
107
107
|
try:
|
108
108
|
credentials = self.store.get(self.name)
|
109
|
-
except KeyNotFoundError:
|
109
|
+
except KeyNotFoundError as e:
|
110
110
|
action = self.authorize()
|
111
|
-
raise NotAuthorizedError(action)
|
111
|
+
raise NotAuthorizedError(action) from e
|
112
112
|
return {"api_key": credentials}
|
113
113
|
|
114
114
|
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
universal_mcp/logger.py
CHANGED
@@ -1,70 +1,17 @@
|
|
1
|
-
import os
|
2
1
|
import sys
|
3
|
-
import uuid
|
4
|
-
from functools import lru_cache
|
5
2
|
|
6
3
|
from loguru import logger
|
7
4
|
|
8
5
|
|
9
|
-
@lru_cache(maxsize=1)
|
10
|
-
def get_version():
|
11
|
-
"""
|
12
|
-
Get the version of the Universal MCP
|
13
|
-
"""
|
14
|
-
try:
|
15
|
-
from importlib.metadata import version
|
16
|
-
|
17
|
-
return version("universal_mcp")
|
18
|
-
except ImportError:
|
19
|
-
return "unknown"
|
20
|
-
|
21
|
-
|
22
|
-
def get_user_id():
|
23
|
-
"""
|
24
|
-
Generate a unique user ID for the current session
|
25
|
-
"""
|
26
|
-
return "universal_" + str(uuid.uuid4())[:8]
|
27
|
-
|
28
|
-
|
29
|
-
def posthog_sink(message, user_id=get_user_id()):
|
30
|
-
"""
|
31
|
-
Custom sink for sending logs to PostHog
|
32
|
-
"""
|
33
|
-
try:
|
34
|
-
import posthog
|
35
|
-
|
36
|
-
posthog.host = "https://us.i.posthog.com"
|
37
|
-
posthog.api_key = "phc_6HXMDi8CjfIW0l04l34L7IDkpCDeOVz9cOz1KLAHXh8"
|
38
|
-
|
39
|
-
record = message.record
|
40
|
-
properties = {
|
41
|
-
"level": record["level"].name,
|
42
|
-
"module": record["name"],
|
43
|
-
"function": record["function"],
|
44
|
-
"line": record["line"],
|
45
|
-
"message": record["message"],
|
46
|
-
"version": get_version(),
|
47
|
-
}
|
48
|
-
posthog.capture(user_id, "universal_mcp", properties)
|
49
|
-
except Exception:
|
50
|
-
# Silently fail if PostHog capture fails - don't want logging to break the app
|
51
|
-
pass
|
52
|
-
|
53
|
-
|
54
6
|
def setup_logger():
|
55
7
|
logger.remove()
|
8
|
+
# STDOUT cant be used as a sink because it will break the stream
|
56
9
|
# logger.add(
|
57
10
|
# sink=sys.stdout,
|
58
|
-
# format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
59
11
|
# level="INFO",
|
60
|
-
# colorize=True,
|
61
12
|
# )
|
13
|
+
# STDERR
|
62
14
|
logger.add(
|
63
15
|
sink=sys.stderr,
|
64
|
-
|
65
|
-
level="WARNING",
|
66
|
-
colorize=True,
|
16
|
+
level="INFO",
|
67
17
|
)
|
68
|
-
telemetry_enabled = os.getenv("TELEMETRY_ENABLED", "true").lower() == "true"
|
69
|
-
if telemetry_enabled:
|
70
|
-
logger.add(posthog_sink, level="INFO") # PostHog telemetry
|
universal_mcp/servers/server.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import os
|
2
2
|
from abc import ABC, abstractmethod
|
3
|
-
from
|
3
|
+
from collections.abc import Callable
|
4
|
+
from typing import Any
|
4
5
|
from urllib.parse import urlparse
|
5
6
|
|
6
7
|
import httpx
|
@@ -8,20 +9,20 @@ from loguru import logger
|
|
8
9
|
from mcp.server.fastmcp import FastMCP
|
9
10
|
from mcp.types import TextContent
|
10
11
|
|
12
|
+
from universal_mcp.analytics import analytics
|
11
13
|
from universal_mcp.applications import Application, app_from_slug
|
12
14
|
from universal_mcp.config import AppConfig, ServerConfig, StoreConfig
|
13
|
-
from universal_mcp.
|
14
|
-
from universal_mcp.integrations import integration_from_config
|
15
|
+
from universal_mcp.integrations import AgentRIntegration, integration_from_config
|
15
16
|
from universal_mcp.stores import BaseStore, store_from_config
|
16
17
|
from universal_mcp.tools.tools import ToolManager
|
17
18
|
|
18
19
|
|
19
20
|
class BaseServer(FastMCP, ABC):
|
20
21
|
"""Base server class with common functionality.
|
21
|
-
|
22
|
+
|
22
23
|
This class provides core server functionality including store setup,
|
23
24
|
tool management, and application loading.
|
24
|
-
|
25
|
+
|
25
26
|
Args:
|
26
27
|
config: Server configuration
|
27
28
|
**kwargs: Additional keyword arguments passed to FastMCP
|
@@ -29,29 +30,12 @@ class BaseServer(FastMCP, ABC):
|
|
29
30
|
|
30
31
|
def __init__(self, config: ServerConfig, **kwargs):
|
31
32
|
super().__init__(config.name, config.description, **kwargs)
|
32
|
-
logger.info(
|
33
|
-
|
33
|
+
logger.info(
|
34
|
+
f"Initializing server: {config.name} ({config.type}) with store: {config.store}"
|
35
|
+
)
|
36
|
+
|
34
37
|
self.config = config # Store config at base level for consistency
|
35
38
|
self._tool_manager = ToolManager(warn_on_duplicate_tools=True)
|
36
|
-
self.store = self._setup_store(config.store)
|
37
|
-
self._load_apps()
|
38
|
-
|
39
|
-
def _setup_store(self, store_config: StoreConfig | None) -> BaseStore | None:
|
40
|
-
"""Setup and configure the store.
|
41
|
-
|
42
|
-
Args:
|
43
|
-
store_config: Store configuration
|
44
|
-
|
45
|
-
Returns:
|
46
|
-
Configured store instance or None if no config provided
|
47
|
-
"""
|
48
|
-
if not store_config:
|
49
|
-
return None
|
50
|
-
|
51
|
-
store = store_from_config(store_config)
|
52
|
-
self.add_tool(store.set)
|
53
|
-
self.add_tool(store.delete)
|
54
|
-
return store
|
55
39
|
|
56
40
|
@abstractmethod
|
57
41
|
def _load_apps(self) -> None:
|
@@ -60,7 +44,7 @@ class BaseServer(FastMCP, ABC):
|
|
60
44
|
|
61
45
|
def add_tool(self, tool: Callable) -> None:
|
62
46
|
"""Add a tool to the server.
|
63
|
-
|
47
|
+
|
64
48
|
Args:
|
65
49
|
tool: Tool to add
|
66
50
|
"""
|
@@ -68,68 +52,57 @@ class BaseServer(FastMCP, ABC):
|
|
68
52
|
|
69
53
|
async def list_tools(self) -> list[dict]:
|
70
54
|
"""List all available tools in MCP format.
|
71
|
-
|
55
|
+
|
72
56
|
Returns:
|
73
57
|
List of tool definitions
|
74
58
|
"""
|
75
|
-
return self._tool_manager.list_tools(format=
|
76
|
-
|
59
|
+
return self._tool_manager.list_tools(format="mcp")
|
60
|
+
|
77
61
|
def _format_tool_result(self, result: Any) -> list[TextContent]:
|
78
62
|
"""Format tool result into TextContent list.
|
79
|
-
|
63
|
+
|
80
64
|
Args:
|
81
65
|
result: Raw tool result
|
82
|
-
|
66
|
+
|
83
67
|
Returns:
|
84
68
|
List of TextContent objects
|
85
69
|
"""
|
86
70
|
if isinstance(result, str):
|
87
71
|
return [TextContent(type="text", text=result)]
|
88
|
-
elif isinstance(result, list) and all(
|
72
|
+
elif isinstance(result, list) and all(
|
73
|
+
isinstance(item, TextContent) for item in result
|
74
|
+
):
|
89
75
|
return result
|
90
76
|
else:
|
91
|
-
logger.warning(
|
77
|
+
logger.warning(
|
78
|
+
f"Tool returned unexpected type: {type(result)}. Wrapping in TextContent."
|
79
|
+
)
|
92
80
|
return [TextContent(type="text", text=str(result))]
|
93
|
-
|
94
|
-
async def call_tool(
|
81
|
+
|
82
|
+
async def call_tool(
|
83
|
+
self, name: str, arguments: dict[str, Any]
|
84
|
+
) -> list[TextContent]:
|
95
85
|
"""Call a tool with comprehensive error handling.
|
96
|
-
|
86
|
+
|
97
87
|
Args:
|
98
88
|
name: Tool name
|
99
89
|
arguments: Tool arguments
|
100
|
-
|
90
|
+
|
101
91
|
Returns:
|
102
92
|
List of TextContent results
|
103
|
-
|
93
|
+
|
104
94
|
Raises:
|
105
95
|
ToolError: If tool execution fails
|
106
96
|
"""
|
107
97
|
logger.info(f"Calling tool: {name} with arguments: {arguments}")
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
return self._format_tool_result(result)
|
112
|
-
|
113
|
-
except ToolError as e:
|
114
|
-
if isinstance(e.__cause__, NotAuthorizedError):
|
115
|
-
message = f"Not authorized to call tool {name}: {e.__cause__.message}"
|
116
|
-
logger.warning(message)
|
117
|
-
return [TextContent(type="text", text=message)]
|
118
|
-
elif isinstance(e.__cause__, httpx.HTTPError):
|
119
|
-
message = f"HTTP error calling tool {name}: {str(e.__cause__)}"
|
120
|
-
logger.error(message)
|
121
|
-
return [TextContent(type="text", text=message)]
|
122
|
-
elif isinstance(e.__cause__, ValueError):
|
123
|
-
message = f"Invalid arguments for tool {name}: {str(e.__cause__)}"
|
124
|
-
logger.error(message)
|
125
|
-
return [TextContent(type="text", text=message)]
|
126
|
-
logger.error(f"Error calling tool {name}: {str(e)}", exc_info=True)
|
127
|
-
raise
|
98
|
+
result = await self._tool_manager.call_tool(name, arguments)
|
99
|
+
logger.info(f"Tool '{name}' completed successfully")
|
100
|
+
return self._format_tool_result(result)
|
128
101
|
|
129
102
|
|
130
103
|
class LocalServer(BaseServer):
|
131
104
|
"""Local development server implementation.
|
132
|
-
|
105
|
+
|
133
106
|
Args:
|
134
107
|
config: Server configuration
|
135
108
|
**kwargs: Additional keyword arguments passed to FastMCP
|
@@ -137,21 +110,42 @@ class LocalServer(BaseServer):
|
|
137
110
|
|
138
111
|
def __init__(self, config: ServerConfig, **kwargs):
|
139
112
|
super().__init__(config, **kwargs)
|
113
|
+
self.store = self._setup_store(config.store)
|
114
|
+
self._load_apps()
|
115
|
+
|
116
|
+
def _setup_store(self, store_config: StoreConfig | None) -> BaseStore | None:
|
117
|
+
"""Setup and configure the store.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
store_config: Store configuration
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
Configured store instance or None if no config provided
|
124
|
+
"""
|
125
|
+
if not store_config:
|
126
|
+
return None
|
127
|
+
|
128
|
+
store = store_from_config(store_config)
|
129
|
+
self.add_tool(store.set)
|
130
|
+
self.add_tool(store.delete)
|
131
|
+
return store
|
140
132
|
|
141
133
|
def _load_app(self, app_config: AppConfig) -> Application | None:
|
142
134
|
"""Load a single application with its integration.
|
143
|
-
|
135
|
+
|
144
136
|
Args:
|
145
137
|
app_config: Application configuration
|
146
|
-
|
138
|
+
|
147
139
|
Returns:
|
148
140
|
Configured application instance or None if loading fails
|
149
141
|
"""
|
150
142
|
try:
|
151
|
-
integration =
|
152
|
-
app_config.integration,
|
153
|
-
|
154
|
-
|
143
|
+
integration = (
|
144
|
+
integration_from_config(app_config.integration, store=self.store)
|
145
|
+
if app_config.integration
|
146
|
+
else None
|
147
|
+
)
|
148
|
+
analytics.track_app_loaded(app_config.name) # Track app loading
|
155
149
|
return app_from_slug(app_config.name)(integration=integration)
|
156
150
|
except Exception as e:
|
157
151
|
logger.error(f"Failed to load app {app_config.name}: {e}", exc_info=True)
|
@@ -168,7 +162,7 @@ class LocalServer(BaseServer):
|
|
168
162
|
|
169
163
|
class AgentRServer(BaseServer):
|
170
164
|
"""AgentR API-connected server implementation.
|
171
|
-
|
165
|
+
|
172
166
|
Args:
|
173
167
|
config: Server configuration
|
174
168
|
api_key: Optional API key for AgentR authentication. If not provided,
|
@@ -179,21 +173,22 @@ class AgentRServer(BaseServer):
|
|
179
173
|
def __init__(self, config: ServerConfig, api_key: str | None = None, **kwargs):
|
180
174
|
self.api_key = api_key or os.getenv("AGENTR_API_KEY")
|
181
175
|
self.base_url = os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
|
182
|
-
|
176
|
+
|
183
177
|
if not self.api_key:
|
184
178
|
raise ValueError("API key required - get one at https://agentr.dev")
|
185
179
|
parsed = urlparse(self.base_url)
|
186
180
|
if not all([parsed.scheme, parsed.netloc]):
|
187
181
|
raise ValueError(f"Invalid base URL format: {self.base_url}")
|
188
|
-
|
189
182
|
super().__init__(config, **kwargs)
|
190
|
-
|
183
|
+
self.integration = AgentRIntegration(name="agentr", api_key=self.api_key)
|
184
|
+
self._load_apps()
|
185
|
+
|
191
186
|
def _fetch_apps(self) -> list[AppConfig]:
|
192
187
|
"""Fetch available apps from AgentR API.
|
193
|
-
|
188
|
+
|
194
189
|
Returns:
|
195
190
|
List of application configurations
|
196
|
-
|
191
|
+
|
197
192
|
Raises:
|
198
193
|
httpx.HTTPError: If API request fails
|
199
194
|
"""
|
@@ -211,18 +206,22 @@ class AgentRServer(BaseServer):
|
|
211
206
|
|
212
207
|
def _load_app(self, app_config: AppConfig) -> Application | None:
|
213
208
|
"""Load a single application with AgentR integration.
|
214
|
-
|
209
|
+
|
215
210
|
Args:
|
216
211
|
app_config: Application configuration
|
217
|
-
|
212
|
+
|
218
213
|
Returns:
|
219
214
|
Configured application instance or None if loading fails
|
220
215
|
"""
|
221
216
|
try:
|
222
|
-
integration =
|
223
|
-
|
224
|
-
|
217
|
+
integration = (
|
218
|
+
AgentRIntegration(
|
219
|
+
name=app_config.integration.name, api_key=self.api_key
|
220
|
+
)
|
221
|
+
if app_config.integration
|
222
|
+
else None
|
225
223
|
)
|
224
|
+
analytics.track_app_loaded(app_config.name) # Track app loading
|
226
225
|
return app_from_slug(app_config.name)(integration=integration)
|
227
226
|
except Exception as e:
|
228
227
|
logger.error(f"Failed to load app {app_config.name}: {e}", exc_info=True)
|
universal_mcp/stores/store.py
CHANGED
@@ -8,11 +8,13 @@ from loguru import logger
|
|
8
8
|
|
9
9
|
class StoreError(Exception):
|
10
10
|
"""Base exception class for store-related errors."""
|
11
|
+
|
11
12
|
pass
|
12
13
|
|
13
14
|
|
14
15
|
class KeyNotFoundError(StoreError):
|
15
16
|
"""Exception raised when a key is not found in the store."""
|
17
|
+
|
16
18
|
pass
|
17
19
|
|
18
20
|
|
@@ -29,10 +31,10 @@ class BaseStore(ABC):
|
|
29
31
|
|
30
32
|
Args:
|
31
33
|
key (str): The key to look up
|
32
|
-
|
34
|
+
|
33
35
|
Returns:
|
34
36
|
Any: The stored value
|
35
|
-
|
37
|
+
|
36
38
|
Raises:
|
37
39
|
KeyNotFoundError: If the key is not found in the store
|
38
40
|
StoreError: If there is an error accessing the store
|
@@ -223,7 +225,7 @@ class KeyringStore(BaseStore):
|
|
223
225
|
keyring.set_password(self.app_name, key, value)
|
224
226
|
except Exception as e:
|
225
227
|
raise StoreError(f"Error storing in keyring: {str(e)}") from e
|
226
|
-
|
228
|
+
|
227
229
|
def delete(self, key: str) -> None:
|
228
230
|
"""
|
229
231
|
Delete a password from the system keyring.
|
universal_mcp/tools/__init__.py
CHANGED
universal_mcp/tools/adapters.py
CHANGED
@@ -5,16 +5,19 @@ def convert_tool_to_mcp_tool(
|
|
5
5
|
tool: Tool,
|
6
6
|
):
|
7
7
|
from mcp.server.fastmcp.server import MCPTool
|
8
|
+
|
8
9
|
return MCPTool(
|
9
10
|
name=tool.name,
|
10
11
|
description=tool.description or "",
|
11
12
|
inputSchema=tool.parameters,
|
12
13
|
)
|
13
14
|
|
15
|
+
|
14
16
|
def convert_tool_to_langchain_tool(
|
15
17
|
tool: Tool,
|
16
18
|
):
|
17
19
|
from langchain_core.tools import StructuredTool
|
20
|
+
|
18
21
|
"""Convert an tool to a LangChain tool.
|
19
22
|
|
20
23
|
NOTE: this tool can be executed only in a context of an active MCP client session.
|
@@ -37,4 +40,4 @@ def convert_tool_to_langchain_tool(
|
|
37
40
|
description=tool.description or "",
|
38
41
|
coroutine=call_tool,
|
39
42
|
response_format="content",
|
40
|
-
)
|
43
|
+
)
|
@@ -135,12 +135,9 @@ class FuncMetadata(BaseModel):
|
|
135
135
|
arbitrary_types_allowed=True,
|
136
136
|
)
|
137
137
|
|
138
|
-
|
139
138
|
@classmethod
|
140
139
|
def func_metadata(
|
141
|
-
cls,
|
142
|
-
func: Callable[..., Any],
|
143
|
-
skip_names: Sequence[str] = ()
|
140
|
+
cls, func: Callable[..., Any], skip_names: Sequence[str] = ()
|
144
141
|
) -> "FuncMetadata":
|
145
142
|
"""Given a function, return metadata including a pydantic model representing its
|
146
143
|
signature.
|
@@ -201,7 +198,10 @@ class FuncMetadata(BaseModel):
|
|
201
198
|
if param.default is not inspect.Parameter.empty
|
202
199
|
else PydanticUndefined,
|
203
200
|
)
|
204
|
-
dynamic_pydantic_model_params[param.name] = (
|
201
|
+
dynamic_pydantic_model_params[param.name] = (
|
202
|
+
field_info.annotation,
|
203
|
+
field_info,
|
204
|
+
)
|
205
205
|
continue
|
206
206
|
|
207
207
|
arguments_model = create_model(
|
@@ -211,4 +211,3 @@ class FuncMetadata(BaseModel):
|
|
211
211
|
)
|
212
212
|
resp = FuncMetadata(arg_model=arguments_model)
|
213
213
|
return resp
|
214
|
-
|