vellum-ai 1.0.7__py3-none-any.whl → 1.0.9__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.
Files changed (24) hide show
  1. vellum/client/core/client_wrapper.py +2 -2
  2. vellum/client/resources/workflows/client.py +8 -0
  3. vellum/client/types/document_document_to_document_index.py +1 -1
  4. vellum/client/types/organization_read.py +2 -0
  5. vellum/client/types/slim_document_document_to_document_index.py +1 -1
  6. vellum/plugins/pydantic.py +9 -2
  7. vellum/workflows/integrations/composio_service.py +12 -0
  8. vellum/workflows/integrations/mcp_service.py +245 -0
  9. vellum/workflows/nodes/displayable/tool_calling_node/node.py +17 -1
  10. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +2 -0
  11. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py +16 -1
  12. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +87 -15
  13. vellum/workflows/types/core.py +6 -2
  14. vellum/workflows/types/definition.py +21 -0
  15. vellum/workflows/types/tests/test_definition.py +79 -1
  16. {vellum_ai-1.0.7.dist-info → vellum_ai-1.0.9.dist-info}/METADATA +1 -1
  17. {vellum_ai-1.0.7.dist-info → vellum_ai-1.0.9.dist-info}/RECORD +24 -23
  18. vellum_cli/push.py +7 -3
  19. vellum_cli/tests/test_push.py +4 -6
  20. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +5 -0
  21. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +5 -0
  22. {vellum_ai-1.0.7.dist-info → vellum_ai-1.0.9.dist-info}/LICENSE +0 -0
  23. {vellum_ai-1.0.7.dist-info → vellum_ai-1.0.9.dist-info}/WHEEL +0 -0
  24. {vellum_ai-1.0.7.dist-info → vellum_ai-1.0.9.dist-info}/entry_points.txt +0 -0
@@ -25,10 +25,10 @@ class BaseClientWrapper:
25
25
 
26
26
  def get_headers(self) -> typing.Dict[str, str]:
27
27
  headers: typing.Dict[str, str] = {
28
- "User-Agent": "vellum-ai/1.0.7",
28
+ "User-Agent": "vellum-ai/1.0.9",
29
29
  "X-Fern-Language": "Python",
30
30
  "X-Fern-SDK-Name": "vellum-ai",
31
- "X-Fern-SDK-Version": "1.0.7",
31
+ "X-Fern-SDK-Version": "1.0.9",
32
32
  }
33
33
  if self._api_version is not None:
34
34
  headers["X-API-Version"] = self._api_version
@@ -27,6 +27,7 @@ class WorkflowsClient:
27
27
  id: str,
28
28
  *,
29
29
  exclude_code: typing.Optional[bool] = None,
30
+ exclude_display: typing.Optional[bool] = None,
30
31
  include_json: typing.Optional[bool] = None,
31
32
  include_sandbox: typing.Optional[bool] = None,
32
33
  strict: typing.Optional[bool] = None,
@@ -40,6 +41,8 @@ class WorkflowsClient:
40
41
 
41
42
  exclude_code : typing.Optional[bool]
42
43
 
44
+ exclude_display : typing.Optional[bool]
45
+
43
46
  include_json : typing.Optional[bool]
44
47
 
45
48
  include_sandbox : typing.Optional[bool]
@@ -60,6 +63,7 @@ class WorkflowsClient:
60
63
  method="GET",
61
64
  params={
62
65
  "exclude_code": exclude_code,
66
+ "exclude_display": exclude_display,
63
67
  "include_json": include_json,
64
68
  "include_sandbox": include_sandbox,
65
69
  "strict": strict,
@@ -165,6 +169,7 @@ class AsyncWorkflowsClient:
165
169
  id: str,
166
170
  *,
167
171
  exclude_code: typing.Optional[bool] = None,
172
+ exclude_display: typing.Optional[bool] = None,
168
173
  include_json: typing.Optional[bool] = None,
169
174
  include_sandbox: typing.Optional[bool] = None,
170
175
  strict: typing.Optional[bool] = None,
@@ -178,6 +183,8 @@ class AsyncWorkflowsClient:
178
183
 
179
184
  exclude_code : typing.Optional[bool]
180
185
 
186
+ exclude_display : typing.Optional[bool]
187
+
181
188
  include_json : typing.Optional[bool]
182
189
 
183
190
  include_sandbox : typing.Optional[bool]
@@ -198,6 +205,7 @@ class AsyncWorkflowsClient:
198
205
  method="GET",
199
206
  params={
200
207
  "exclude_code": exclude_code,
208
+ "exclude_display": exclude_display,
201
209
  "include_json": include_json,
202
210
  "include_sandbox": include_sandbox,
203
211
  "strict": strict,
@@ -22,7 +22,7 @@ class DocumentDocumentToDocumentIndex(UniversalBaseModel):
22
22
  Vellum-generated ID that uniquely identifies the environment index this document is included in.
23
23
  """
24
24
 
25
- document_index_id: str = pydantic.Field()
25
+ document_index_id: typing.Optional[str] = pydantic.Field(default=None)
26
26
  """
27
27
  Vellum-generated ID that uniquely identifies the index this document is included in.
28
28
  """
@@ -2,6 +2,7 @@
2
2
 
3
3
  from ..core.pydantic_utilities import UniversalBaseModel
4
4
  import typing
5
+ import datetime as dt
5
6
  from .new_member_join_behavior_enum import NewMemberJoinBehaviorEnum
6
7
  from ..core.pydantic_utilities import IS_PYDANTIC_V2
7
8
  import pydantic
@@ -10,6 +11,7 @@ import pydantic
10
11
  class OrganizationRead(UniversalBaseModel):
11
12
  id: str
12
13
  name: str
14
+ created: typing.Optional[dt.datetime] = None
13
15
  allow_staff_access: typing.Optional[bool] = None
14
16
  new_member_join_behavior: NewMemberJoinBehaviorEnum
15
17
  limit_config: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None
@@ -22,7 +22,7 @@ class SlimDocumentDocumentToDocumentIndex(UniversalBaseModel):
22
22
  Vellum-generated ID that uniquely identifies the environment index this document is included in.
23
23
  """
24
24
 
25
- document_index_id: str = pydantic.Field()
25
+ document_index_id: typing.Optional[str] = pydantic.Field(default=None)
26
26
  """
27
27
  Vellum-generated ID that uniquely identifies the index this document is included in.
28
28
  """
@@ -28,9 +28,12 @@ def import_base_descriptor():
28
28
  """
29
29
  We have to avoid importing from vellum.* in this file because it will cause a circular import.
30
30
  """
31
- from vellum.workflows.descriptors.base import BaseDescriptor
31
+ try:
32
+ from vellum.workflows.descriptors.base import BaseDescriptor
32
33
 
33
- return BaseDescriptor
34
+ return BaseDescriptor
35
+ except Exception:
36
+ return None
34
37
 
35
38
 
36
39
  # https://docs.pydantic.dev/2.8/concepts/plugins/#build-a-plugin
@@ -60,6 +63,10 @@ class OnValidatePython(ValidatePythonHandlerProtocol):
60
63
  self.tracked_descriptors = {}
61
64
  BaseDescriptor = import_base_descriptor()
62
65
 
66
+ # If BaseDescriptor import failed, skip descriptor processing
67
+ if BaseDescriptor is None:
68
+ return
69
+
63
70
  for key, value in input.items():
64
71
  field_info = model_fields.get(key)
65
72
  if isinstance(value, BaseDescriptor) and (
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional
5
5
 
6
6
  import requests
7
7
 
8
+ from vellum.workflows.errors.types import WorkflowErrorCode
8
9
  from vellum.workflows.exceptions import NodeException
9
10
 
10
11
  logger = logging.getLogger(__name__)
@@ -72,6 +73,17 @@ class ComposioService:
72
73
 
73
74
  response.raise_for_status()
74
75
  return response.json()
76
+ except requests.exceptions.HTTPError as e:
77
+ if e.response.status_code == 401:
78
+ raise NodeException(
79
+ message="Failed to authorize Composio request. Make sure to define a COMPOSIO_API_KEY",
80
+ code=WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE,
81
+ )
82
+ else:
83
+ response_text = e.response.text if e.response else "No response"
84
+ raise NodeException(
85
+ f"Composio API request failed with status {e.response.status_code}: {response_text}"
86
+ )
75
87
  except Exception as e:
76
88
  raise NodeException(f"Composio API request failed: {e}")
77
89
 
@@ -0,0 +1,245 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import httpx
7
+
8
+ from vellum.workflows.constants import AuthorizationType
9
+ from vellum.workflows.errors.types import WorkflowErrorCode
10
+ from vellum.workflows.exceptions import NodeException
11
+ from vellum.workflows.types.core import VellumSecret
12
+ from vellum.workflows.types.definition import MCPServer, MCPToolDefinition
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class MCPHttpClient:
18
+ """
19
+ Direct HTTP implementation for MCP (Model Context Protocol) client
20
+ without using the official Python SDK.
21
+
22
+ Supports Streamable HTTP transport using httpx.
23
+ """
24
+
25
+ def __init__(self, server_url: str, headers: Dict[str, str], session_timeout: int = 30):
26
+ """
27
+ Initialize MCP HTTP client.
28
+
29
+ Args:
30
+ server_url: The MCP server endpoint URL (e.g., "https://example.com/mcp")
31
+ headers: Authentication headers
32
+ session_timeout: Timeout for HTTP requests in seconds
33
+ """
34
+ self.server_url = server_url.rstrip("/")
35
+ self.headers = headers
36
+ self.session_timeout = session_timeout
37
+ self.session_id: Optional[str] = None
38
+ self.request_id = 0
39
+ self._client: Optional[httpx.AsyncClient] = None
40
+
41
+ async def __aenter__(self):
42
+ """Async context manager entry."""
43
+ self._client = httpx.AsyncClient(timeout=httpx.Timeout(self.session_timeout), headers=self.headers)
44
+ return self
45
+
46
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
47
+ """Async context manager exit."""
48
+ if self._client:
49
+ await self._client.aclose()
50
+
51
+ def _next_request_id(self) -> int:
52
+ """Generate next request ID."""
53
+ self.request_id += 1
54
+ return self.request_id
55
+
56
+ async def _send_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
57
+ """
58
+ Send a JSON-RPC request to the MCP server.
59
+
60
+ Args:
61
+ method: The JSON-RPC method name
62
+ params: Optional parameters for the method
63
+
64
+ Returns:
65
+ The JSON-RPC response
66
+ """
67
+ if not self._client:
68
+ raise RuntimeError("Client session not initialized. Use 'async with' context manager.")
69
+
70
+ # Prepare JSON-RPC request
71
+ request_data = {"jsonrpc": "2.0", "id": self._next_request_id(), "method": method, "params": params or {}}
72
+
73
+ # Prepare headers
74
+ headers = {
75
+ "Content-Type": "application/json",
76
+ "Accept": "application/json",
77
+ }
78
+
79
+ # Include session ID if we have one
80
+ if self.session_id:
81
+ headers["Mcp-Session-Id"] = self.session_id
82
+
83
+ logger.debug(f"Sending request: {json.dumps(request_data, indent=2)}")
84
+
85
+ # Send POST request
86
+ response = await self._client.post(self.server_url, json=request_data, headers=headers)
87
+
88
+ # Check for session ID in response headers
89
+ if "Mcp-Session-Id" in response.headers:
90
+ self.session_id = response.headers["Mcp-Session-Id"]
91
+ logger.debug(f"Received session ID: {self.session_id}")
92
+
93
+ # Handle JSON response
94
+ response_data = response.json()
95
+ logger.debug(f"Received response: {json.dumps(response_data, indent=2)}")
96
+
97
+ if "error" in response_data:
98
+ raise Exception(f"MCP Error: {response_data['error']}")
99
+
100
+ return response_data
101
+
102
+ async def initialize(self, client_info: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
103
+ """
104
+ Initialize the MCP connection.
105
+
106
+ Args:
107
+ client_info: Optional client information
108
+
109
+ Returns:
110
+ Server capabilities and information
111
+ """
112
+ params = {
113
+ "protocolVersion": "2025-06-18",
114
+ "capabilities": {},
115
+ "clientInfo": client_info or {"name": "vellum-mcp-client", "version": "1.0.0"},
116
+ }
117
+
118
+ response = await self._send_request("initialize", params)
119
+ return response.get("result", {})
120
+
121
+ async def list_tools(self) -> List[Dict[str, Any]]:
122
+ """
123
+ Get list of available tools from the server.
124
+
125
+ Returns:
126
+ List of tool definitions
127
+ """
128
+ response = await self._send_request("tools/list")
129
+ return response.get("result", {}).get("tools", [])
130
+
131
+ async def call_tool(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
132
+ """
133
+ Call a tool on the server.
134
+
135
+ Args:
136
+ name: Tool name
137
+ arguments: Tool arguments
138
+
139
+ Returns:
140
+ Tool execution result
141
+ """
142
+ params = {"name": name, "arguments": arguments}
143
+
144
+ response = await self._send_request("tools/call", params)
145
+ return response.get("result", {})
146
+
147
+
148
+ class MCPService:
149
+ def _get_auth_headers(self, server: MCPServer) -> Dict[str, str]:
150
+ headers = {}
151
+ if server.authorization_type == AuthorizationType.BEARER_TOKEN:
152
+ token = server.bearer_token_value
153
+ if not token:
154
+ raise NodeException(
155
+ "Bearer token is required for BEARER_TOKEN authorization type",
156
+ code=WorkflowErrorCode.INVALID_INPUTS,
157
+ )
158
+
159
+ headers["Authorization"] = f"Bearer {token}"
160
+
161
+ elif server.authorization_type == AuthorizationType.API_KEY:
162
+ key = server.api_key_header_key
163
+ value = server.api_key_header_value
164
+ if not key or not value:
165
+ raise NodeException(
166
+ "API key header key and value are required for API_KEY authorization type",
167
+ code=WorkflowErrorCode.INVALID_INPUTS,
168
+ )
169
+ if isinstance(value, VellumSecret):
170
+ headers[key] = value.name
171
+ elif isinstance(value, str):
172
+ headers[key] = value
173
+
174
+ return headers
175
+
176
+ async def _execute_mcp_call(self, server: MCPServer, operation: str, **kwargs) -> Any:
177
+ """Execute an MCP operation using direct HTTP calls."""
178
+ headers = self._get_auth_headers(server)
179
+
180
+ try:
181
+ async with MCPHttpClient(server.url, headers) as client:
182
+ await client.initialize()
183
+
184
+ if operation == "list_tools":
185
+ return await client.list_tools()
186
+ elif operation == "call_tool":
187
+ return await client.call_tool(
188
+ name=kwargs["name"],
189
+ arguments=kwargs["arguments"],
190
+ )
191
+ else:
192
+ raise ValueError(f"Unknown MCP operation: {operation}")
193
+
194
+ except Exception as e:
195
+ logger.error(f"Error executing MCP operation {operation}: {e}")
196
+ raise NodeException(
197
+ message=f"Error executing MCP operation '{operation}': {str(e)}",
198
+ code=WorkflowErrorCode.NODE_EXECUTION,
199
+ )
200
+
201
+ def list_tools(self, server: MCPServer) -> List[Dict[str, Any]]:
202
+ """List available tools from an MCP server."""
203
+ try:
204
+ tools = asyncio.run(self._execute_mcp_call(server, "list_tools"))
205
+ return tools
206
+ except Exception as e:
207
+ logger.warning(f"Failed to list tools from MCP server '{server.name}': {e}")
208
+ return []
209
+
210
+ def execute_tool(self, tool_def: MCPToolDefinition, arguments: Dict[str, Any]) -> Any:
211
+ """Execute a tool on an MCP server."""
212
+ try:
213
+ result = asyncio.run(
214
+ self._execute_mcp_call(
215
+ tool_def.server,
216
+ "call_tool",
217
+ name=tool_def.name,
218
+ arguments=arguments,
219
+ )
220
+ )
221
+ return result
222
+ except Exception as e:
223
+ logger.error(f"Error executing MCP tool '{tool_def.name}': {e}")
224
+ raise NodeException(
225
+ message=f"Error executing MCP tool '{tool_def.name}': {str(e)}",
226
+ code=WorkflowErrorCode.NODE_EXECUTION,
227
+ )
228
+
229
+ def hydrate_tool_definitions(self, server_def: MCPServer) -> List[MCPToolDefinition]:
230
+ """Hydrate an MCPToolDefinition with detailed information from the MCP server."""
231
+ try:
232
+ tools = self.list_tools(server_def)
233
+
234
+ return [
235
+ MCPToolDefinition(
236
+ name=tool["name"],
237
+ server=server_def,
238
+ description=tool["description"],
239
+ parameters=tool["inputSchema"],
240
+ )
241
+ for tool in tools
242
+ ]
243
+ except Exception as e:
244
+ logger.warning(f"Failed to hydrate MCP server '{server_def.name}': {e}")
245
+ return []
@@ -15,10 +15,12 @@ from vellum.workflows.nodes.displayable.tool_calling_node.utils import (
15
15
  create_function_node,
16
16
  create_tool_router_node,
17
17
  get_function_name,
18
+ hydrate_mcp_tool_definitions,
18
19
  )
19
20
  from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
20
21
  from vellum.workflows.state.context import WorkflowContext
21
- from vellum.workflows.types.core import EntityInputsInterface, Tool
22
+ from vellum.workflows.types.core import EntityInputsInterface, Tool, ToolSource
23
+ from vellum.workflows.types.definition import MCPServer
22
24
  from vellum.workflows.workflows.event_filters import all_workflow_event_filter
23
25
 
24
26
 
@@ -30,6 +32,7 @@ class ToolCallingNode(BaseNode):
30
32
  ml_model: str - The model to use for tool calling (e.g., "gpt-4o-mini")
31
33
  blocks: List[PromptBlock] - The prompt blocks to use (same format as InlinePromptNode)
32
34
  functions: List[Tool] - The functions that can be called
35
+ tool_sources: List[ToolSource] - The tool sources that can be called
33
36
  prompt_inputs: Optional[EntityInputsInterface] - Mapping of input variable names to values
34
37
  parameters: PromptParameters - The parameters for the Prompt
35
38
  max_prompt_iterations: Optional[int] - Maximum number of prompt iterations before stopping
@@ -38,6 +41,7 @@ class ToolCallingNode(BaseNode):
38
41
  ml_model: ClassVar[str] = "gpt-4o-mini"
39
42
  blocks: ClassVar[List[Union[PromptBlock, Dict[str, Any]]]] = []
40
43
  functions: ClassVar[List[Tool]] = []
44
+ tool_sources: ClassVar[List[ToolSource]] = []
41
45
  prompt_inputs: ClassVar[Optional[EntityInputsInterface]] = None
42
46
  parameters: PromptParameters = DEFAULT_PROMPT_PARAMETERS
43
47
  max_prompt_iterations: ClassVar[Optional[int]] = 5
@@ -134,6 +138,7 @@ class ToolCallingNode(BaseNode):
134
138
  ml_model=self.ml_model,
135
139
  blocks=self.blocks,
136
140
  functions=self.functions,
141
+ tool_sources=self.tool_sources,
137
142
  prompt_inputs=self.prompt_inputs,
138
143
  parameters=self.parameters,
139
144
  max_prompt_iterations=self.max_prompt_iterations,
@@ -148,6 +153,17 @@ class ToolCallingNode(BaseNode):
148
153
  tool_router_node=self.tool_router_node,
149
154
  )
150
155
 
156
+ for tool_source in self.tool_sources:
157
+ if isinstance(tool_source, MCPServer):
158
+ tool_definitions = hydrate_mcp_tool_definitions(tool_source)
159
+ for tool_definition in tool_definitions:
160
+ function_name = get_function_name(tool_definition)
161
+
162
+ self._function_nodes[function_name] = create_function_node(
163
+ function=tool_definition,
164
+ tool_router_node=self.tool_router_node,
165
+ )
166
+
151
167
  graph_set = set()
152
168
 
153
169
  # Add connections from ports of router to function nodes and back to router
@@ -39,6 +39,7 @@ def test_port_condition_match_function_name():
39
39
  ml_model="test-model",
40
40
  blocks=[],
41
41
  functions=[first_function, second_function],
42
+ tool_sources=[],
42
43
  prompt_inputs=None,
43
44
  parameters=DEFAULT_PROMPT_PARAMETERS,
44
45
  )
@@ -97,6 +98,7 @@ def test_tool_calling_node_inline_workflow_context():
97
98
  ml_model="test-model",
98
99
  blocks=[],
99
100
  functions=[MyWorkflow],
101
+ tool_sources=[],
100
102
  prompt_inputs=None,
101
103
  parameters=DEFAULT_PROMPT_PARAMETERS,
102
104
  )
@@ -15,7 +15,7 @@ from vellum.workflows.nodes.bases import BaseNode
15
15
  from vellum.workflows.nodes.displayable.tool_calling_node.utils import create_tool_router_node, get_function_name
16
16
  from vellum.workflows.outputs.base import BaseOutputs
17
17
  from vellum.workflows.state.base import BaseState
18
- from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
18
+ from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer, MCPToolDefinition
19
19
 
20
20
 
21
21
  def test_get_function_name_callable():
@@ -69,6 +69,19 @@ def test_get_function_name_subworkflow_deployment_uuid():
69
69
  assert result == "57f09bebb46340e0bf9ec972e664352f"
70
70
 
71
71
 
72
+ def test_get_function_name_mcp_tool_definition():
73
+ """Test MCPToolDefinition function name generation."""
74
+ mcp_tool = MCPToolDefinition(
75
+ name="create_repository",
76
+ server=MCPServer(name="github", url="https://api.github.com"),
77
+ parameters={"repository_name": "string", "description": "string"},
78
+ )
79
+
80
+ result = get_function_name(mcp_tool)
81
+
82
+ assert result == "github__create_repository"
83
+
84
+
72
85
  @pytest.mark.parametrize(
73
86
  "toolkit,action,description,expected_result",
74
87
  [
@@ -93,6 +106,7 @@ def test_create_tool_router_node_max_prompt_iterations(vellum_adhoc_prompt_clien
93
106
  ml_model="gpt-4o-mini",
94
107
  blocks=[],
95
108
  functions=[],
109
+ tool_sources=[],
96
110
  prompt_inputs=None,
97
111
  parameters=DEFAULT_PROMPT_PARAMETERS,
98
112
  max_prompt_iterations=None,
@@ -152,6 +166,7 @@ def test_create_tool_router_node_chat_history_block_dict(vellum_adhoc_prompt_cli
152
166
  ml_model="gpt-4o-mini",
153
167
  blocks=blocks, # type: ignore
154
168
  functions=[],
169
+ tool_sources=[],
155
170
  prompt_inputs=None,
156
171
  parameters=DEFAULT_PROMPT_PARAMETERS,
157
172
  )
@@ -17,6 +17,7 @@ from vellum.workflows.exceptions import NodeException
17
17
  from vellum.workflows.expressions.concat import ConcatExpression
18
18
  from vellum.workflows.inputs import BaseInputs
19
19
  from vellum.workflows.integrations.composio_service import ComposioService
20
+ from vellum.workflows.integrations.mcp_service import MCPService
20
21
  from vellum.workflows.nodes.bases import BaseNode
21
22
  from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
22
23
  from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
@@ -27,8 +28,8 @@ from vellum.workflows.ports.port import Port
27
28
  from vellum.workflows.references.lazy import LazyReference
28
29
  from vellum.workflows.state import BaseState
29
30
  from vellum.workflows.state.encoder import DefaultStateEncoder
30
- from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior, Tool
31
- from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
31
+ from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior, Tool, ToolSource
32
+ from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer, MCPToolDefinition
32
33
  from vellum.workflows.types.generics import is_workflow_class
33
34
 
34
35
  CHAT_HISTORY_VARIABLE = "chat_history"
@@ -196,6 +197,29 @@ class ComposioNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
196
197
  yield from []
197
198
 
198
199
 
200
+ class MCPNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
201
+ """Node that executes an MCP tool with function call output."""
202
+
203
+ mcp_tool: MCPToolDefinition
204
+
205
+ def run(self) -> Iterator[BaseOutput]:
206
+ arguments = self._extract_function_arguments()
207
+
208
+ try:
209
+ mcp_service = MCPService()
210
+ result = mcp_service.execute_tool(tool_def=self.mcp_tool, arguments=arguments)
211
+ except Exception as e:
212
+ raise NodeException(
213
+ message=f"Error executing MCP tool '{self.mcp_tool.name}': {str(e)}",
214
+ code=WorkflowErrorCode.NODE_EXECUTION,
215
+ )
216
+
217
+ # Add result to chat history
218
+ self._add_function_result_to_chat_history(result, self.state)
219
+
220
+ yield from []
221
+
222
+
199
223
  def _hydrate_composio_tool_definition(tool_def: ComposioToolDefinition) -> ComposioToolDefinition:
200
224
  """Hydrate a ComposioToolDefinition with detailed information from the Composio API.
201
225
 
@@ -233,19 +257,53 @@ def _hydrate_composio_tool_definition(tool_def: ComposioToolDefinition) -> Compo
233
257
  return tool_def
234
258
 
235
259
 
260
+ def hydrate_mcp_tool_definitions(server_def: MCPServer) -> List[MCPToolDefinition]:
261
+ """Hydrate an MCPToolDefinition with detailed information from the MCP server.
262
+
263
+ We do tool discovery on the MCP server to get the tool definitions.
264
+
265
+ Args:
266
+ tool_def: The basic MCPToolDefinition to enhance
267
+
268
+ Returns:
269
+ MCPToolDefinition with detailed parameters and description
270
+ """
271
+ try:
272
+ mcp_service = MCPService()
273
+ return mcp_service.hydrate_tool_definitions(server_def)
274
+ except Exception as e:
275
+ # If hydration fails, log and return original
276
+ logger.warning(f"Failed to enhance MCP server '{server_def.name}': {e}")
277
+ return []
278
+
279
+
236
280
  def create_tool_router_node(
237
281
  ml_model: str,
238
282
  blocks: List[Union[PromptBlock, Dict[str, Any]]],
239
283
  functions: List[Tool],
284
+ tool_sources: List[ToolSource],
240
285
  prompt_inputs: Optional[EntityInputsInterface],
241
286
  parameters: PromptParameters,
242
287
  max_prompt_iterations: Optional[int] = None,
243
288
  ) -> Type[ToolRouterNode]:
244
- if functions and len(functions) > 0:
289
+ if functions and len(functions) > 0 or tool_sources and len(tool_sources) > 0:
245
290
  # Create dynamic ports and convert functions in a single loop
246
291
  Ports = type("Ports", (), {})
247
292
  prompt_functions: List[Union[Tool, FunctionDefinition]] = []
248
293
 
294
+ # Avoid using lambda to capture function_name
295
+ # lambda will capture the function_name by reference,
296
+ # and if the function_name is changed, the port_condition will also change.
297
+ def create_port_condition(fn_name):
298
+ return Port.on_if(
299
+ LazyReference(
300
+ lambda: (
301
+ node.Outputs.results[0]["type"].equals("FUNCTION_CALL")
302
+ & node.Outputs.results[0]["value"]["name"].equals(fn_name)
303
+ )
304
+ )
305
+ )
306
+
249
307
  for function in functions:
250
308
  if isinstance(function, ComposioToolDefinition):
251
309
  # Get Composio tool details and hydrate the function definition
@@ -262,21 +320,22 @@ def create_tool_router_node(
262
320
 
263
321
  # Create port for this function (using original function for get_function_name)
264
322
  function_name = get_function_name(function)
323
+ port = create_port_condition(function_name)
324
+ setattr(Ports, function_name, port)
265
325
 
266
- # Avoid using lambda to capture function_name
267
- # lambda will capture the function_name by reference,
268
- # and if the function_name is changed, the port_condition will also change.
269
- def create_port_condition(fn_name):
270
- return LazyReference(
271
- lambda: (
272
- node.Outputs.results[0]["type"].equals("FUNCTION_CALL")
273
- & node.Outputs.results[0]["value"]["name"].equals(fn_name)
326
+ for server in tool_sources:
327
+ tool_functions = hydrate_mcp_tool_definitions(server)
328
+ for tool_function in tool_functions:
329
+ name = get_function_name(tool_function)
330
+ prompt_functions.append(
331
+ FunctionDefinition(
332
+ name=name,
333
+ description=tool_function.description,
334
+ parameters=tool_function.parameters,
274
335
  )
275
336
  )
276
-
277
- port_condition = create_port_condition(function_name)
278
- port = Port.on_if(port_condition)
279
- setattr(Ports, function_name, port)
337
+ port = create_port_condition(name)
338
+ setattr(Ports, name, port)
280
339
 
281
340
  # Add the else port for when no function conditions match
282
341
  setattr(Ports, "default", Port.on_else())
@@ -379,6 +438,17 @@ def create_function_node(
379
438
  },
380
439
  )
381
440
  return node
441
+ elif isinstance(function, MCPToolDefinition):
442
+ node = type(
443
+ f"MCPNode_{function.name}",
444
+ (MCPNode,),
445
+ {
446
+ "mcp_tool": function,
447
+ "function_call_output": tool_router_node.Outputs.results,
448
+ "__module__": __name__,
449
+ },
450
+ )
451
+ return node
382
452
  elif is_workflow_class(function):
383
453
  node = type(
384
454
  f"DynamicInlineSubworkflowNode_{function.__name__}",
@@ -410,5 +480,7 @@ def get_function_name(function: Tool) -> str:
410
480
  return name.replace("-", "")
411
481
  elif isinstance(function, ComposioToolDefinition):
412
482
  return function.name
483
+ elif isinstance(function, MCPToolDefinition):
484
+ return f"{function.server.name}__{function.name}"
413
485
  else:
414
486
  return snake_case(function.__name__)
@@ -13,7 +13,7 @@ from typing import ( # type: ignore[attr-defined]
13
13
  )
14
14
 
15
15
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
16
- from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
16
+ from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer, MCPToolDefinition
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from vellum.workflows.workflows.base import BaseWorkflow
@@ -41,6 +41,7 @@ class MergeBehavior(Enum):
41
41
  AWAIT_ALL = "AWAIT_ALL"
42
42
  AWAIT_ANY = "AWAIT_ANY"
43
43
  AWAIT_ATTRIBUTES = "AWAIT_ATTRIBUTES"
44
+ CUSTOM = "CUSTOM"
44
45
 
45
46
 
46
47
  class ConditionType(Enum):
@@ -50,4 +51,7 @@ class ConditionType(Enum):
50
51
 
51
52
 
52
53
  # Type alias for functions that can be called in tool calling nodes
53
- Tool = Union[Callable[..., Any], DeploymentDefinition, Type["BaseWorkflow"], ComposioToolDefinition]
54
+ Tool = Union[Callable[..., Any], DeploymentDefinition, Type["BaseWorkflow"], ComposioToolDefinition, MCPToolDefinition]
55
+
56
+ # Type alias for sources that provide tools to tool calling nodes
57
+ ToolSource = Union[MCPServer]
@@ -8,6 +8,9 @@ from pydantic import BeforeValidator
8
8
 
9
9
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
10
10
  from vellum.client.types.code_resource_definition import CodeResourceDefinition as ClientCodeResourceDefinition
11
+ from vellum.client.types.vellum_secret import VellumSecret
12
+ from vellum.workflows.constants import AuthorizationType
13
+ from vellum.workflows.references.environment_variable import EnvironmentVariableReference
11
14
 
12
15
 
13
16
  def serialize_type_encoder(obj: type) -> Dict[str, Any]:
@@ -118,3 +121,21 @@ class ComposioToolDefinition(UniversalBaseModel):
118
121
  def name(self) -> str:
119
122
  """Generate a function name for this tool"""
120
123
  return self.action.lower()
124
+
125
+
126
+ class MCPServer(UniversalBaseModel):
127
+ name: str
128
+ url: str
129
+ authorization_type: AuthorizationType = AuthorizationType.BEARER_TOKEN
130
+ bearer_token_value: Optional[Union[str, EnvironmentVariableReference]] = None
131
+ api_key_header_key: Optional[str] = None
132
+ api_key_header_value: Optional[Union[str, VellumSecret]] = None
133
+
134
+ model_config = {"arbitrary_types_allowed": True}
135
+
136
+
137
+ class MCPToolDefinition(UniversalBaseModel):
138
+ name: str
139
+ server: MCPServer
140
+ description: Optional[str] = None
141
+ parameters: Dict[str, Any] = {}
@@ -1,7 +1,10 @@
1
1
  import pytest
2
2
  from uuid import UUID
3
3
 
4
- from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
4
+ from vellum.client.types.vellum_secret import VellumSecret
5
+ from vellum.workflows.constants import AuthorizationType
6
+ from vellum.workflows.references.environment_variable import EnvironmentVariableReference
7
+ from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer, MCPToolDefinition
5
8
 
6
9
 
7
10
  @pytest.mark.parametrize(
@@ -44,3 +47,78 @@ def test_composio_tool_definition_creation():
44
47
  assert composio_tool.description == "Create a new issue in a GitHub repository"
45
48
  assert composio_tool.display_name is None
46
49
  assert composio_tool.name == "github_create_an_issue"
50
+
51
+
52
+ def test_mcp_tool_definition_creation_bearer_token():
53
+ """Test that MCPToolDefinition can be created with required fields."""
54
+ mcp_tool = MCPToolDefinition(
55
+ name="create_repository",
56
+ server=MCPServer(
57
+ name="github",
58
+ url="https://api.githubcopilot.com/mcp/",
59
+ authorization_type=AuthorizationType.BEARER_TOKEN,
60
+ bearer_token_value=EnvironmentVariableReference(name="GITHUB_PERSONAL_ACCESS_TOKEN"),
61
+ ),
62
+ parameters={
63
+ "type": "object",
64
+ "properties": {
65
+ "repository_name": {"type": "string", "description": "Repository name"},
66
+ "description": {"type": "string", "description": "Repository description"},
67
+ },
68
+ "required": ["repository_name"],
69
+ },
70
+ )
71
+
72
+ assert mcp_tool.name == "create_repository"
73
+ assert mcp_tool.server.name == "github"
74
+ assert mcp_tool.server.url == "https://api.githubcopilot.com/mcp/"
75
+ assert mcp_tool.server.authorization_type == AuthorizationType.BEARER_TOKEN
76
+ assert mcp_tool.parameters == {
77
+ "type": "object",
78
+ "properties": {
79
+ "repository_name": {"type": "string", "description": "Repository name"},
80
+ "description": {"type": "string", "description": "Repository description"},
81
+ },
82
+ "required": ["repository_name"],
83
+ }
84
+
85
+ assert isinstance(mcp_tool.server.bearer_token_value, EnvironmentVariableReference)
86
+ assert mcp_tool.server.bearer_token_value.name == "GITHUB_PERSONAL_ACCESS_TOKEN"
87
+
88
+
89
+ def test_mcp_tool_definition_creation_api_key():
90
+ """Test that MCPToolDefinition can be created with required fields."""
91
+ mcp_tool = MCPToolDefinition(
92
+ name="create_repository",
93
+ server=MCPServer(
94
+ name="github",
95
+ url="https://api.githubcopilot.com/mcp/",
96
+ authorization_type=AuthorizationType.API_KEY,
97
+ api_key_header_key="Authorization",
98
+ api_key_header_value=VellumSecret(name="GITHUB_PERSONAL_ACCESS_TOKEN"),
99
+ ),
100
+ parameters={
101
+ "type": "object",
102
+ "properties": {
103
+ "repository_name": {"type": "string", "description": "Repository name"},
104
+ "description": {"type": "string", "description": "Repository description"},
105
+ },
106
+ "required": ["repository_name"],
107
+ },
108
+ )
109
+
110
+ assert mcp_tool.name == "create_repository"
111
+ assert mcp_tool.server.name == "github"
112
+ assert mcp_tool.server.url == "https://api.githubcopilot.com/mcp/"
113
+ assert mcp_tool.server.authorization_type == AuthorizationType.API_KEY
114
+ assert mcp_tool.server.api_key_header_key == "Authorization"
115
+ assert isinstance(mcp_tool.server.api_key_header_value, VellumSecret)
116
+ assert mcp_tool.server.api_key_header_value.name == "GITHUB_PERSONAL_ACCESS_TOKEN"
117
+ assert mcp_tool.parameters == {
118
+ "type": "object",
119
+ "properties": {
120
+ "repository_name": {"type": "string", "description": "Repository name"},
121
+ "description": {"type": "string", "description": "Repository description"},
122
+ },
123
+ "required": ["repository_name"],
124
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.0.7
3
+ Version: 1.0.9
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -8,7 +8,7 @@ vellum_cli/init.py,sha256=WpnMXPItPmh0f0bBGIer3p-e5gu8DUGwSArT_FuoMEw,5093
8
8
  vellum_cli/logger.py,sha256=dcM_OmgqXLo93vDYswO5ylyUQQcTfnA5GTd5tbIt3wM,1446
9
9
  vellum_cli/ping.py,sha256=p_BCCRjgPhng6JktuECtkDQLbhopt6JpmrtGoLnLJT8,1161
10
10
  vellum_cli/pull.py,sha256=udYyPlJ6VKDdh78rApNJOZgxHl82fcV6iGnRPSdX1LY,14750
11
- vellum_cli/push.py,sha256=d1947A_QwFKSZyDimK6dEK1abf_o8e1uzjpI_A_SCwM,11459
11
+ vellum_cli/push.py,sha256=5sGkWmwnuPI9mYlBFZO4Wc0WcErZAnFNXZYJByiAJGw,11620
12
12
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  vellum_cli/tests/conftest.py,sha256=wx3PlJjVB0HRf5dr2b_idOIw27WPPl0J0FNbhIJJaVk,1689
14
14
  vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
@@ -18,7 +18,7 @@ vellum_cli/tests/test_init.py,sha256=8UOc_ThfouR4ja5cCl_URuLk7ohr9JXfCnG4yka1OUQ
18
18
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
19
19
  vellum_cli/tests/test_ping.py,sha256=b3aQLd-N59_8w2rRiWqwpB1rlHaKEYVbAj1Y3hi7A-g,2605
20
20
  vellum_cli/tests/test_pull.py,sha256=hxMbW_j0weDDrkzVGpvLpFcwNQdn-fxTv4wBHeYizzc,49904
21
- vellum_cli/tests/test_push.py,sha256=H9ZU0_E-I1F98SBvj-I_1ooe3RzOlhMTTmEFg6CRrYY,40384
21
+ vellum_cli/tests/test_push.py,sha256=9whdm-i4d3scJeJ7snGrkCVKcDlZsq8WqH_Nnv9KAYk,40395
22
22
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -95,8 +95,8 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_
95
95
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py,sha256=V8b6gKghLlO7PJI8xeNdnfn8aII0W_IFQvSQBQM62UQ,7721
96
96
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py,sha256=hDWtKXmGI1CKhTwTNqpu_d5RkE5n7SolMLtgd87KqTI,3856
97
97
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py,sha256=gonapBCyDDt3qc7U02PCuKyPS8f3YiSAZ7QD86CH1Fw,3794
98
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py,sha256=4t1lkN2nsZF6lFqP6QnskUQWJlhasF8C2_f6atzk8ZY,26298
99
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=B0rDsCvO24qPp0gkmj8SdTDY5CxZYkvKwknsKBuAPyA,10017
98
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py,sha256=pZJVn7oXyTuKvyLOM6pj00ulSJR2QtVd9h2VwBv4_mg,26522
99
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=wKJlecy8kxlOslTlPHRFUb-zFBGpNq6S7eeOrswB9-I,10241
100
100
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py,sha256=mova0sPD3evHiHIN1O0VynxlCp-uOcEIKve5Pd_oCDg,4069
101
101
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=pLCyMScV88DTBXRH7jXaXOEA1GBq8NIipCUFwIAWnwI,2771
102
102
  vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py,sha256=J4ouI8KxbMfxQP2Zq_9cWMGYgbjCWmKzjCJEtnSJb0I,5829
@@ -145,7 +145,7 @@ vellum/client/README.md,sha256=Dle5iytCXxP1pNeNd7uZyhFo0rl7tp7vU7s8gmi10OQ,4863
145
145
  vellum/client/__init__.py,sha256=KmkyOgReuTsjmXF3WC_dPQ9QqJgYrB3Sr8_LcSUIQyI,125258
146
146
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
147
147
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
148
- vellum/client/core/client_wrapper.py,sha256=4HIoFVgVHLgCMAKnGSlXru4ZVIHjc4LSqpYHg9GPlAI,2383
148
+ vellum/client/core/client_wrapper.py,sha256=K6kF1J_NBmrtjJs0H4OqVoVsJgY3ivrRe4FzhceFTis,2383
149
149
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
150
150
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
151
151
  vellum/client/core/http_client.py,sha256=cKs2w0ybDBk1wHQf-fTALm_MmvaMe3cZKcYJxqmCxkE,19539
@@ -210,7 +210,7 @@ vellum/client/resources/workflow_sandboxes/client.py,sha256=tpIjyuvVAnghmUu_gI9l
210
210
  vellum/client/resources/workflow_sandboxes/types/__init__.py,sha256=EaGVRU1w6kJiiHrbZOeEa0c3ggjfgv_jBqsyOkCRWOI,212
211
211
  vellum/client/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py,sha256=TEwWit20W3X-zWPPLAhmUG05UudG9gaBSJ4Q4-rNJws,188
212
212
  vellum/client/resources/workflows/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
213
- vellum/client/resources/workflows/client.py,sha256=a3DwkUn8FBve-Wr139MSms18EPZPdnTkDsMqyTUQEYc,10725
213
+ vellum/client/resources/workflows/client.py,sha256=rU5fJmVebWO6SoK5S9RzJHM0-hucRKnDt24tvG-DwEM,11037
214
214
  vellum/client/resources/workspace_secrets/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
215
215
  vellum/client/resources/workspace_secrets/client.py,sha256=l1FOj0f-IB5_oQ7iWiHopFK3lDXBqiaIc9g10W9PHFU,8381
216
216
  vellum/client/resources/workspaces/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
@@ -295,7 +295,7 @@ vellum/client/types/deployment_release_tag_read.py,sha256=dUrTOz9LH1gAvC_ktMB_7N
295
295
  vellum/client/types/docker_service_token.py,sha256=T0icNHBKsIs6TrEiDRjckM_f37hcF1DMwEE8161tTvY,614
296
296
  vellum/client/types/document_chat_message_content.py,sha256=MiVYuMKtRUaT6_ve1MzihChb10SrOt_0VhpCB0x7hFQ,745
297
297
  vellum/client/types/document_chat_message_content_request.py,sha256=wMzj1SREQUeiSqZhOoLOOQzn6hmO_GDOMICDQ4fEXzs,774
298
- vellum/client/types/document_document_to_document_index.py,sha256=IDfBoch4lYL-J1XlUNSAtV7elJ6l5jX5peboYsriGjQ,1580
298
+ vellum/client/types/document_document_to_document_index.py,sha256=Yeb84grNv-T9nI2oFjMwioPHUEH2TLREaqhldWMYwhs,1609
299
299
  vellum/client/types/document_index_chunking.py,sha256=TU0Y7z0Xacm3dhzEDuDIG3ZKJCu3vNURRh3PqEd17mY,356
300
300
  vellum/client/types/document_index_chunking_request.py,sha256=g9BKCsHKg5kzjG7YYeMNQ_5R8TXLeSgumJlMXoSfBcs,435
301
301
  vellum/client/types/document_index_indexing_config.py,sha256=xL1pCzUOkw5sSie1OrBpasE3bVnv0UyZBn7uZztbhbs,781
@@ -500,7 +500,7 @@ vellum/client/types/open_ai_vectorizer_text_embedding_3_small.py,sha256=T_-P7qGj
500
500
  vellum/client/types/open_ai_vectorizer_text_embedding_3_small_request.py,sha256=-lwNeWj7ExP-JLncUp1Uyd20FxweVIDu-aEnenPB98A,841
501
501
  vellum/client/types/open_ai_vectorizer_text_embedding_ada_002.py,sha256=c4vNlR6lRvUjq-67M06sroDMNMG_qC4JUBqwmKEJQ2I,812
502
502
  vellum/client/types/open_ai_vectorizer_text_embedding_ada_002_request.py,sha256=FdpkkNBGgRwfqFjBwpfH4t2zKIM0pIYminX2iZQUzvY,841
503
- vellum/client/types/organization_read.py,sha256=QDFpX4pZCjGSRXZ6FF65SDzRxFqkI87DEEUXtaVoTAs,837
503
+ vellum/client/types/organization_read.py,sha256=AMTjqWH6CWXzbPpjoOE9Usb0lsqepC_vBfqKjLxHcik,908
504
504
  vellum/client/types/paginated_container_image_read_list.py,sha256=7lwIgs1q7Z0xDYPGWPnjSNC1kU_peu79CotzaaQfRdA,801
505
505
  vellum/client/types/paginated_deployment_release_tag_read_list.py,sha256=hp7D74CxPY14dEPRZ-fnTCwp63upxkYquL1e74oYXh4,826
506
506
  vellum/client/types/paginated_document_index_read_list.py,sha256=bO7pm3KCZi5LDO17YXgr_lUF9SRdAfMu6wOutX91ANw,797
@@ -589,7 +589,7 @@ vellum/client/types/sentence_chunking.py,sha256=guqU3072X4h8Laf6LhTWQ5lpjBpTgoXR
589
589
  vellum/client/types/sentence_chunking_request.py,sha256=77gv1fVc9IaTuGGx3O1HB0LF9sXM5pSTWksl8BEmvLU,812
590
590
  vellum/client/types/slim_deployment_read.py,sha256=Gr3wWT1eJKe33MVsW0O0pSIgUF_LE6WFbvFbf5_CVb8,1654
591
591
  vellum/client/types/slim_document.py,sha256=HJiymYPvRxfxhBUkD8epW0hQ2Vt9PQtv398QsRb4DsI,2395
592
- vellum/client/types/slim_document_document_to_document_index.py,sha256=IG8gzxWO32fzjd6bQn5ZyBoIlAGpkx1RNgjsFaWUXSY,1522
592
+ vellum/client/types/slim_document_document_to_document_index.py,sha256=0LThZhOgIE2kSmIWH9s59_6pEzGRWpx5qS5BBuWXdLk,1551
593
593
  vellum/client/types/slim_release_review.py,sha256=7DXmD1AVa_Wj7e0qiR7GUN9cSqwkk1JloYmp_3oluQQ,783
594
594
  vellum/client/types/slim_workflow_deployment.py,sha256=Js-ycMFZ8-kNFPsd4bZew9nI_eN2M_58LzDHeCjkfTg,2009
595
595
  vellum/client/types/slim_workflow_execution_read.py,sha256=Opm1HTYVMz_D2USQCB-5ZoJ4EjKKfrDhoXc0hETldVM,1936
@@ -844,7 +844,7 @@ vellum/evaluations/utils/env.py,sha256=Xj_nxsoU5ox06EOTjRopR4lrigQI6Le6qbWGltYoE
844
844
  vellum/evaluations/utils/exceptions.py,sha256=dXMAkzqbHV_AP5FjjbegPlfUE0zQDlpA3qOsoOJUxfg,49
845
845
  vellum/evaluations/utils/paginator.py,sha256=rEED_BJAXAM6tM1yMwHePNzszjq_tTq4NbQvi1jWQ_Q,697
846
846
  vellum/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
847
- vellum/plugins/pydantic.py,sha256=GmNsxupKskbqpe4N5NBmSnLo680EATqhXJHABgf1NO0,3727
847
+ vellum/plugins/pydantic.py,sha256=SamPlRZ8V9kuxEfMkOPKjhMMLa5Q3wYJ3Z-F_IfKtvA,3911
848
848
  vellum/plugins/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
849
849
  vellum/plugins/tests/test_pydantic.py,sha256=S6bLqs3Y5DGA012QV_7f6hk4e6Bz-iJ9py9DEKuo4fM,746
850
850
  vellum/plugins/utils.py,sha256=cPmxE9R2CK1bki2jKE8rB-G9zMf2pzHjSPDHFPXwd3Q,878
@@ -1597,7 +1597,8 @@ vellum/workflows/inputs/base.py,sha256=w3owT5B3rLBmIj-v-jL2l-HD4yd3hXK9RmHVd557B
1597
1597
  vellum/workflows/inputs/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1598
1598
  vellum/workflows/inputs/tests/test_inputs.py,sha256=lioA8917mFLYq7Ml69UNkqUjcWbbxkxnpIEJ4FBaYBk,2206
1599
1599
  vellum/workflows/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1600
- vellum/workflows/integrations/composio_service.py,sha256=p3V9l7OlOV1zZK9aIgvfhLiDsxbxeSVZJ3MBSedo7Pw,5008
1600
+ vellum/workflows/integrations/composio_service.py,sha256=Wo1IKHr_CzrHFQHR_hjVcKap3JLwlV_TEbKsZcuR6Sw,5664
1601
+ vellum/workflows/integrations/mcp_service.py,sha256=SaOLg76JBAiBDAMUn04mxVWmf2Btobd1kDjc8B1atng,8712
1601
1602
  vellum/workflows/logging.py,sha256=_a217XogktV4Ncz6xKFz7WfYmZAzkfVRVuC0rWob8ls,437
1602
1603
  vellum/workflows/nodes/__init__.py,sha256=aVdQVv7Y3Ro3JlqXGpxwaU2zrI06plDHD2aumH5WUIs,1157
1603
1604
  vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62ErdVpCbWICX64,58
@@ -1695,13 +1696,13 @@ vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py,sha2
1695
1696
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
1696
1697
  vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=dc3EEn1sOICpr3GdS8eyeFtExaGwWWcw9eHSdkRhQJU,2584
1697
1698
  vellum/workflows/nodes/displayable/tool_calling_node/__init__.py,sha256=3n0-ysmFKsr40CVxPthc0rfJgqVJeZuUEsCmYudLVRg,117
1698
- vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=KRI1NMgXZTUgQqq9uOA9W_D8k8sy7ZAq6v53-YVno1k,6545
1699
+ vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=6uFt-_woayqyvbVGNJeWoiBQs2_VBtgTTdMX-Ztwx1I,7338
1699
1700
  vellum/workflows/nodes/displayable/tool_calling_node/state.py,sha256=oQg_GAtc349nPB5BL_oeDYYD7q1qSDPAqjj8iA8OoAw,215
1700
1701
  vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1701
1702
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=UV0vZpU7-_tHcwnIq36WKwHrJXNurU4bdC3rfaw8eoU,4804
1702
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=raY_E5-EgtYNXEPbO2I-Ythe4YeuFdGsXGZ_BAN98uI,7979
1703
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=Ku_fUUoqQFeKLZ6o1DPCi7ax9PdkbaxkEEj6rAwjytM,7858
1704
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=uRZGCJA3FBRt1ZPQWOF2R8D49aajrW4Yjxvn3kIcrAQ,16545
1703
+ vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=U14GS6YwJGpIA13nq4Qvo1YBEku5ZRL627ZWnybp4hg,8029
1704
+ vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=3TQeLD-HlB0OHEtUcADe4jl0XGi9YTv5FL4ZupDaSJA,8361
1705
+ vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=shFC4Vm0vyqAILWsUTDOo_Ggn0_7GHKQlgZ0TPHohWQ,19180
1705
1706
  vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrraiBHDLKTlnBa51ZiE,284
1706
1707
  vellum/workflows/nodes/experimental/__init__.py,sha256=jCQgvZEknXKfuNhGSOou4XPfrPqZ1_XBj5F0n0fgiWM,106
1707
1708
  vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
@@ -1748,12 +1749,12 @@ vellum/workflows/tests/test_sandbox.py,sha256=JKwaluI-lODQo7Ek9sjDstjL_WTdSqUlVi
1748
1749
  vellum/workflows/tests/test_undefined.py,sha256=zMCVliCXVNLrlC6hEGyOWDnQADJ2g83yc5FIM33zuo8,353
1749
1750
  vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
1750
1751
  vellum/workflows/types/code_execution_node_wrappers.py,sha256=3MNIoFZKzVzNS5qFLVuDwMV17QJw72zo7NRf52yMq5A,3074
1751
- vellum/workflows/types/core.py,sha256=6MW_BRLcx4oEJpItQWQa64xfCrsk76suZSsMKKEsJLg,1314
1752
- vellum/workflows/types/definition.py,sha256=K1evpjoxHpZysx8HBcA-IY0fS_p1afS4QcvG2HZ-r0o,3815
1752
+ vellum/workflows/types/core.py,sha256=ybQj70QjlVNSEXsdIO8Mug9EBIDEkyFPxYv4cnn_hM0,1482
1753
+ vellum/workflows/types/definition.py,sha256=pK0fAXHw7C0AFpCoM4WGe1_MD-usupF4-m6ldo5AQXY,4568
1753
1754
  vellum/workflows/types/generics.py,sha256=8jptbEx1fnJV0Lhj0MpCJOT6yNiEWeTOYOwrEAb5CRU,1576
1754
1755
  vellum/workflows/types/stack.py,sha256=h7NE0vXR7l9DevFBIzIAk1Zh59K-kECQtDTKOUunwMY,1314
1755
1756
  vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1756
- vellum/workflows/types/tests/test_definition.py,sha256=c3GczPtWxuH3BOULwZacxYTQlP2ryqH7rvT_anDORVo,1604
1757
+ vellum/workflows/types/tests/test_definition.py,sha256=RsDoicu8A1dqJOGa-Ok866K8lnzn5L0Hez3lQijYD4c,5011
1757
1758
  vellum/workflows/types/tests/test_utils.py,sha256=UnZog59tR577mVwqZRqqWn2fScoOU1H6up0EzS8zYhw,2536
1758
1759
  vellum/workflows/types/utils.py,sha256=mTctHITBybpt4855x32oCKALBEcMNLn-9cCmfEKgJHQ,6498
1759
1760
  vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1774,8 +1775,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1774
1775
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1775
1776
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
1776
1777
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1777
- vellum_ai-1.0.7.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1778
- vellum_ai-1.0.7.dist-info/METADATA,sha256=-x_ul0guh3O6FHWJQn5INiN0RWn4W4IBRFk_JZgcUGQ,5554
1779
- vellum_ai-1.0.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1780
- vellum_ai-1.0.7.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1781
- vellum_ai-1.0.7.dist-info/RECORD,,
1778
+ vellum_ai-1.0.9.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1779
+ vellum_ai-1.0.9.dist-info/METADATA,sha256=b1ciqHMubElgtkn0rm_nW7vFBfZ2KqdOVuogZ9ICOcE,5554
1780
+ vellum_ai-1.0.9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1781
+ vellum_ai-1.0.9.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1782
+ vellum_ai-1.0.9.dist-info/RECORD,,
vellum_cli/push.py CHANGED
@@ -67,7 +67,10 @@ def push_command(
67
67
  raise ValueError(f"No workflow config for '{module}' found in project to push.")
68
68
 
69
69
  if len(workflow_configs) > 1:
70
- raise ValueError("Multiple workflows found in project to push. Pushing only a single workflow is supported.")
70
+ raise ValueError(
71
+ "Multiple workflows found. Please specify a single workflow to push. "
72
+ f"Found: {', '.join([w.module for w in workflow_configs])}"
73
+ )
71
74
 
72
75
  workflow_config = workflow_configs[0]
73
76
 
@@ -95,8 +98,9 @@ def push_command(
95
98
  and workflow_config.workflow_sandbox_id
96
99
  and workflow_config.workflow_sandbox_id != workflow_sandbox_id
97
100
  ):
98
- raise ValueError(
99
- f"Workflow sandbox id '{workflow_sandbox_id}' is already associated with '{workflow_config.module}'."
101
+ logger.warning(
102
+ f"Workflow sandbox id '{workflow_sandbox_id}' is already associated with '{workflow_config.module}'. "
103
+ f"Continuing with the provided workflow sandbox id '{workflow_sandbox_id}'."
100
104
  )
101
105
 
102
106
  client = create_vellum_client(
@@ -77,9 +77,8 @@ def test_push__multiple_workflows_configured__no_module_specified(mock_module):
77
77
  # THEN it should fail
78
78
  assert result.exit_code == 1
79
79
  assert result.exception
80
- assert (
81
- str(result.exception)
82
- == "Multiple workflows found in project to push. Pushing only a single workflow is supported."
80
+ assert str(result.exception) == (
81
+ "Multiple workflows found. Please specify a single workflow to push. Found: examples.mock, examples.mock2"
83
82
  )
84
83
 
85
84
 
@@ -349,9 +348,8 @@ def test_push__workflow_sandbox_option__existing_id_different_module(mock_module
349
348
  # THEN it should fail
350
349
  assert result.exit_code == 1
351
350
  assert result.exception
352
- assert (
353
- str(result.exception)
354
- == "Multiple workflows found in project to push. Pushing only a single workflow is supported."
351
+ assert str(result.exception) == (
352
+ f"Multiple workflows found. Please specify a single workflow to push. Found: {module}, {second_module}"
355
353
  )
356
354
 
357
355
 
@@ -407,6 +407,11 @@ def test_serialize_workflow():
407
407
  ],
408
408
  },
409
409
  },
410
+ {
411
+ "id": "89084be2-0853-4483-9031-c24bdf872c32",
412
+ "name": "tool_sources",
413
+ "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": []}},
414
+ },
410
415
  {
411
416
  "id": "229cd1ca-dc2f-4586-b933-c4d4966f7bd1",
412
417
  "name": "parameters",
@@ -175,6 +175,11 @@ def test_serialize_workflow():
175
175
  ],
176
176
  },
177
177
  },
178
+ {
179
+ "id": "89084be2-0853-4483-9031-c24bdf872c32",
180
+ "name": "tool_sources",
181
+ "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": []}},
182
+ },
178
183
  {
179
184
  "id": "229cd1ca-dc2f-4586-b933-c4d4966f7bd1",
180
185
  "name": "parameters",