griptape-nodes 0.56.0__py3-none-any.whl → 0.56.1__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.
- griptape_nodes/app/app.py +10 -15
- griptape_nodes/app/watch.py +35 -67
- griptape_nodes/bootstrap/utils/__init__.py +1 -0
- griptape_nodes/bootstrap/utils/python_subprocess_executor.py +122 -0
- griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +418 -0
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +37 -8
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +326 -0
- griptape_nodes/bootstrap/workflow_executors/utils/__init__.py +1 -0
- griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +51 -0
- griptape_nodes/bootstrap/workflow_publishers/__init__.py +1 -0
- griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +43 -0
- griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +84 -0
- griptape_nodes/bootstrap/workflow_publishers/utils/__init__.py +1 -0
- griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +54 -0
- griptape_nodes/cli/commands/engine.py +4 -15
- griptape_nodes/cli/main.py +6 -1
- griptape_nodes/exe_types/core_types.py +26 -0
- griptape_nodes/exe_types/node_types.py +116 -1
- griptape_nodes/retained_mode/events/agent_events.py +2 -0
- griptape_nodes/retained_mode/events/base_events.py +18 -17
- griptape_nodes/retained_mode/events/execution_events.py +3 -1
- griptape_nodes/retained_mode/events/flow_events.py +5 -7
- griptape_nodes/retained_mode/events/mcp_events.py +363 -0
- griptape_nodes/retained_mode/events/node_events.py +3 -4
- griptape_nodes/retained_mode/griptape_nodes.py +8 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +67 -4
- griptape_nodes/retained_mode/managers/event_manager.py +31 -13
- griptape_nodes/retained_mode/managers/flow_manager.py +76 -44
- griptape_nodes/retained_mode/managers/library_manager.py +7 -9
- griptape_nodes/retained_mode/managers/mcp_manager.py +364 -0
- griptape_nodes/retained_mode/managers/node_manager.py +12 -1
- griptape_nodes/retained_mode/managers/settings.py +40 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +94 -8
- griptape_nodes/traits/multi_options.py +5 -1
- griptape_nodes/traits/options.py +10 -2
- {griptape_nodes-0.56.0.dist-info → griptape_nodes-0.56.1.dist-info}/METADATA +2 -2
- {griptape_nodes-0.56.0.dist-info → griptape_nodes-0.56.1.dist-info}/RECORD +39 -26
- {griptape_nodes-0.56.0.dist-info → griptape_nodes-0.56.1.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.56.0.dist-info → griptape_nodes-0.56.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) server management events."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, TypedDict
|
|
5
|
+
|
|
6
|
+
from griptape_nodes.retained_mode.events.base_events import (
|
|
7
|
+
RequestPayload,
|
|
8
|
+
ResultPayloadFailure,
|
|
9
|
+
ResultPayloadSuccess,
|
|
10
|
+
WorkflowAlteredMixin,
|
|
11
|
+
WorkflowNotAlteredMixin,
|
|
12
|
+
)
|
|
13
|
+
from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Type definitions for better clarity
|
|
17
|
+
class MCPServerConfig(TypedDict, total=False):
|
|
18
|
+
"""Configuration for an MCP server."""
|
|
19
|
+
|
|
20
|
+
name: str
|
|
21
|
+
transport: str # Transport protocol type
|
|
22
|
+
enabled: bool
|
|
23
|
+
command: str | None # Process command for stdio transport
|
|
24
|
+
args: list[str] | None # Process arguments for stdio transport
|
|
25
|
+
env: dict[str, str] | None # Environment variables for stdio transport
|
|
26
|
+
cwd: str | None # Working directory for stdio transport
|
|
27
|
+
encoding: str # Text encoding for stdio transport
|
|
28
|
+
encoding_error_handler: str # Error handling strategy for stdio transport
|
|
29
|
+
url: str | None # Connection URL for HTTP-based transports
|
|
30
|
+
headers: dict[str, str] | None # HTTP headers for HTTP-based transports
|
|
31
|
+
timeout: float | None # Connection timeout for HTTP-based transports
|
|
32
|
+
sse_read_timeout: float | None # Read timeout for SSE transport
|
|
33
|
+
terminate_on_close: bool # Session termination behavior for streamable HTTP transport
|
|
34
|
+
description: str | None
|
|
35
|
+
capabilities: list[str] | None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MCPServerCapability(TypedDict):
|
|
39
|
+
"""Information about an MCP server capability."""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
description: str | None
|
|
43
|
+
input_schema: dict[str, Any] | None # JSON schema for capability inputs
|
|
44
|
+
output_schema: dict[str, Any] | None # JSON schema for capability outputs
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MCPServerInfo(TypedDict, total=False):
|
|
48
|
+
"""Information about an MCP server."""
|
|
49
|
+
|
|
50
|
+
name: str
|
|
51
|
+
version: str | None # Server version identifier
|
|
52
|
+
description: str | None # Human-readable server description
|
|
53
|
+
capabilities: list[str] | None # List of supported capability names
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# MCP Server Management Events
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Capability Discovery Events
|
|
60
|
+
@dataclass
|
|
61
|
+
@PayloadRegistry.register
|
|
62
|
+
class DiscoverMCPServerCapabilitiesRequest(RequestPayload):
|
|
63
|
+
"""Discover capabilities from a running MCP server.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
name: The MCP server identifier to discover capabilities from
|
|
67
|
+
timeout: Maximum time to wait for server response in seconds
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
name: str
|
|
71
|
+
timeout: int = 30
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
@PayloadRegistry.register
|
|
76
|
+
class DiscoverMCPServerCapabilitiesResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
77
|
+
"""MCP server capabilities discovered successfully."""
|
|
78
|
+
|
|
79
|
+
name: str
|
|
80
|
+
capabilities: list[str]
|
|
81
|
+
detailed_tools: list[MCPServerCapability] | None = None
|
|
82
|
+
server_info: MCPServerInfo | None = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
@PayloadRegistry.register
|
|
87
|
+
class DiscoverMCPServerCapabilitiesResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
88
|
+
"""Failed to discover MCP server capabilities."""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
@PayloadRegistry.register
|
|
93
|
+
class ListMCPServersRequest(RequestPayload):
|
|
94
|
+
"""List all configured MCP servers.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
include_disabled: Whether to include disabled servers in the results
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
include_disabled: bool = False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
@PayloadRegistry.register
|
|
105
|
+
class ListMCPServersResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
106
|
+
"""MCP servers listed successfully."""
|
|
107
|
+
|
|
108
|
+
servers: dict[str, MCPServerConfig]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
@PayloadRegistry.register
|
|
113
|
+
class ListMCPServersResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
114
|
+
"""Failed to list MCP servers."""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
@PayloadRegistry.register
|
|
119
|
+
class GetMCPServerRequest(RequestPayload):
|
|
120
|
+
"""Get configuration for a specific MCP server.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
name: The unique identifier for the MCP server
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
name: str
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass
|
|
130
|
+
@PayloadRegistry.register
|
|
131
|
+
class GetMCPServerResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
132
|
+
"""MCP server configuration retrieved successfully."""
|
|
133
|
+
|
|
134
|
+
server_config: MCPServerConfig
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass
|
|
138
|
+
@PayloadRegistry.register
|
|
139
|
+
class GetMCPServerResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
140
|
+
"""Failed to get MCP server configuration."""
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
@PayloadRegistry.register
|
|
145
|
+
class CreateMCPServerRequest(RequestPayload):
|
|
146
|
+
"""Create a new MCP server configuration.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
name: Unique identifier for the server
|
|
150
|
+
transport: Transport protocol type
|
|
151
|
+
command: Process command to start the server (required for stdio transport)
|
|
152
|
+
args: Process arguments to pass to the command (stdio transport)
|
|
153
|
+
env: Environment variables for the server process (stdio transport)
|
|
154
|
+
cwd: Working directory for the server process (stdio transport)
|
|
155
|
+
encoding: Text encoding for stdio communication
|
|
156
|
+
encoding_error_handler: Encoding error handling strategy for stdio
|
|
157
|
+
url: Connection URL for HTTP-based transports
|
|
158
|
+
headers: HTTP headers for HTTP-based connections
|
|
159
|
+
timeout: Connection timeout in seconds
|
|
160
|
+
sse_read_timeout: SSE read timeout in seconds
|
|
161
|
+
terminate_on_close: Session termination behavior for streamable HTTP transport
|
|
162
|
+
description: Optional description of the server
|
|
163
|
+
capabilities: List of server capabilities
|
|
164
|
+
enabled: Whether the server is enabled by default
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
name: str
|
|
168
|
+
transport: str = "stdio"
|
|
169
|
+
enabled: bool = True
|
|
170
|
+
|
|
171
|
+
# StdioConnection fields
|
|
172
|
+
command: str | None = None
|
|
173
|
+
args: list[str] | None = None
|
|
174
|
+
env: dict[str, str] | None = None
|
|
175
|
+
cwd: str | None = None
|
|
176
|
+
encoding: str = "utf-8"
|
|
177
|
+
encoding_error_handler: str = "strict"
|
|
178
|
+
|
|
179
|
+
# HTTP-based connection fields
|
|
180
|
+
url: str | None = None
|
|
181
|
+
headers: dict[str, str] | None = None
|
|
182
|
+
timeout: float | None = None
|
|
183
|
+
sse_read_timeout: float | None = None
|
|
184
|
+
terminate_on_close: bool = True
|
|
185
|
+
|
|
186
|
+
# Common fields
|
|
187
|
+
description: str | None = None
|
|
188
|
+
capabilities: list[str] | None = None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass
|
|
192
|
+
@PayloadRegistry.register
|
|
193
|
+
class CreateMCPServerResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
194
|
+
"""MCP server created successfully."""
|
|
195
|
+
|
|
196
|
+
name: str
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
@PayloadRegistry.register
|
|
201
|
+
class CreateMCPServerResultFailure(WorkflowAlteredMixin, ResultPayloadFailure):
|
|
202
|
+
"""Failed to create MCP server."""
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
@PayloadRegistry.register
|
|
207
|
+
class UpdateMCPServerRequest(RequestPayload):
|
|
208
|
+
"""Update an existing MCP server configuration.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
name: The unique identifier for the MCP server
|
|
212
|
+
new_name: Updated display name for the server
|
|
213
|
+
transport: Updated transport protocol type
|
|
214
|
+
command: Updated process command to start the server (stdio transport)
|
|
215
|
+
args: Updated process arguments to pass to the command (stdio transport)
|
|
216
|
+
env: Updated environment variables for the server process (stdio transport)
|
|
217
|
+
cwd: Updated working directory for the server process (stdio transport)
|
|
218
|
+
encoding: Updated text encoding for stdio communication
|
|
219
|
+
encoding_error_handler: Updated encoding error handling strategy for stdio
|
|
220
|
+
url: Updated connection URL for HTTP-based transports
|
|
221
|
+
headers: Updated HTTP headers for HTTP-based connections
|
|
222
|
+
timeout: Updated connection timeout in seconds
|
|
223
|
+
sse_read_timeout: Updated SSE read timeout in seconds
|
|
224
|
+
terminate_on_close: Updated session termination behavior for streamable HTTP transport
|
|
225
|
+
description: Updated description of the server
|
|
226
|
+
capabilities: Updated list of server capabilities
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
name: str
|
|
230
|
+
new_name: str | None = None
|
|
231
|
+
transport: str | None = None
|
|
232
|
+
enabled: bool | None = None
|
|
233
|
+
|
|
234
|
+
# StdioConnection fields
|
|
235
|
+
command: str | None = None
|
|
236
|
+
args: list[str] | None = None
|
|
237
|
+
env: dict[str, str] | None = None
|
|
238
|
+
cwd: str | None = None
|
|
239
|
+
encoding: str | None = None
|
|
240
|
+
encoding_error_handler: str | None = None
|
|
241
|
+
|
|
242
|
+
# HTTP-based connection fields
|
|
243
|
+
url: str | None = None
|
|
244
|
+
headers: dict[str, str] | None = None
|
|
245
|
+
timeout: float | None = None
|
|
246
|
+
sse_read_timeout: float | None = None
|
|
247
|
+
terminate_on_close: bool | None = None
|
|
248
|
+
|
|
249
|
+
# Common fields
|
|
250
|
+
description: str | None = None
|
|
251
|
+
capabilities: list[str] | None = None
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@dataclass
|
|
255
|
+
@PayloadRegistry.register
|
|
256
|
+
class UpdateMCPServerResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
257
|
+
"""MCP server updated successfully."""
|
|
258
|
+
|
|
259
|
+
name: str
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass
|
|
263
|
+
@PayloadRegistry.register
|
|
264
|
+
class UpdateMCPServerResultFailure(WorkflowAlteredMixin, ResultPayloadFailure):
|
|
265
|
+
"""Failed to update MCP server."""
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@dataclass
|
|
269
|
+
@PayloadRegistry.register
|
|
270
|
+
class DeleteMCPServerRequest(RequestPayload):
|
|
271
|
+
"""Delete an MCP server configuration.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
name: The unique identifier for the MCP server to delete
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
name: str
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dataclass
|
|
281
|
+
@PayloadRegistry.register
|
|
282
|
+
class DeleteMCPServerResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
283
|
+
"""MCP server deleted successfully."""
|
|
284
|
+
|
|
285
|
+
name: str
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@dataclass
|
|
289
|
+
@PayloadRegistry.register
|
|
290
|
+
class DeleteMCPServerResultFailure(WorkflowAlteredMixin, ResultPayloadFailure):
|
|
291
|
+
"""Failed to delete MCP server."""
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dataclass
|
|
295
|
+
@PayloadRegistry.register
|
|
296
|
+
class EnableMCPServerRequest(RequestPayload):
|
|
297
|
+
"""Enable an MCP server.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
name: The unique identifier for the MCP server to enable
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
name: str
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@dataclass
|
|
307
|
+
@PayloadRegistry.register
|
|
308
|
+
class EnableMCPServerResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
309
|
+
"""MCP server enabled successfully."""
|
|
310
|
+
|
|
311
|
+
name: str
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@dataclass
|
|
315
|
+
@PayloadRegistry.register
|
|
316
|
+
class EnableMCPServerResultFailure(WorkflowAlteredMixin, ResultPayloadFailure):
|
|
317
|
+
"""Failed to enable MCP server."""
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@dataclass
|
|
321
|
+
@PayloadRegistry.register
|
|
322
|
+
class DisableMCPServerRequest(RequestPayload):
|
|
323
|
+
"""Disable an MCP server.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
name: The unique identifier for the MCP server to disable
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
name: str
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@dataclass
|
|
333
|
+
@PayloadRegistry.register
|
|
334
|
+
class DisableMCPServerResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
335
|
+
"""MCP server disabled successfully."""
|
|
336
|
+
|
|
337
|
+
name: str
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@dataclass
|
|
341
|
+
@PayloadRegistry.register
|
|
342
|
+
class DisableMCPServerResultFailure(WorkflowAlteredMixin, ResultPayloadFailure):
|
|
343
|
+
"""Failed to disable MCP server."""
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@dataclass
|
|
347
|
+
@PayloadRegistry.register
|
|
348
|
+
class GetEnabledMCPServersRequest(RequestPayload):
|
|
349
|
+
"""Get all enabled MCP servers."""
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@dataclass
|
|
353
|
+
@PayloadRegistry.register
|
|
354
|
+
class GetEnabledMCPServersResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
355
|
+
"""Enabled MCP servers retrieved successfully."""
|
|
356
|
+
|
|
357
|
+
servers: dict[str, MCPServerConfig]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@dataclass
|
|
361
|
+
@PayloadRegistry.register
|
|
362
|
+
class GetEnabledMCPServersResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
363
|
+
"""Failed to get enabled MCP servers."""
|
|
@@ -4,8 +4,7 @@ from typing import Any, NamedTuple, NewType
|
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
6
|
from griptape_nodes.exe_types.core_types import NodeMessagePayload
|
|
7
|
-
from griptape_nodes.exe_types.node_types import NodeResolutionState
|
|
8
|
-
from griptape_nodes.node_library.library_registry import LibraryNameAndVersion
|
|
7
|
+
from griptape_nodes.exe_types.node_types import NodeDependencies, NodeResolutionState
|
|
9
8
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
10
9
|
RequestPayload,
|
|
11
10
|
ResultPayloadFailure,
|
|
@@ -409,7 +408,7 @@ class SerializedNodeCommands:
|
|
|
409
408
|
Attributes:
|
|
410
409
|
create_node_command (CreateNodeRequest): The command to create the node.
|
|
411
410
|
element_modification_commands (list[RequestPayload]): A list of commands to create or modify the elements (including Parameters) of the node.
|
|
412
|
-
|
|
411
|
+
node_dependencies (NodeDependencies): Dependencies that this node has on external resources (workflows, files, imports, libraries).
|
|
413
412
|
node_uuid (NodeUUID): The UUID of this particular node. During deserialization, this UUID will be used to correlate this node's instance
|
|
414
413
|
with the connections and parameter values necessary. We cannot use node name because Griptape Nodes enforces unique names, and we cannot
|
|
415
414
|
predict the name that will be selected upon instantiation. Similarly, the same serialized node may be deserialized multiple times, such
|
|
@@ -435,7 +434,7 @@ class SerializedNodeCommands:
|
|
|
435
434
|
|
|
436
435
|
create_node_command: CreateNodeRequest
|
|
437
436
|
element_modification_commands: list[RequestPayload]
|
|
438
|
-
|
|
437
|
+
node_dependencies: NodeDependencies
|
|
439
438
|
lock_node_command: SetLockNodeStateRequest | None = None
|
|
440
439
|
node_uuid: NodeUUID = field(default_factory=lambda: SerializedNodeCommands.NodeUUID(str(uuid4())))
|
|
441
440
|
|
|
@@ -62,6 +62,7 @@ if TYPE_CHECKING:
|
|
|
62
62
|
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
63
63
|
from griptape_nodes.retained_mode.managers.flow_manager import FlowManager
|
|
64
64
|
from griptape_nodes.retained_mode.managers.library_manager import LibraryManager
|
|
65
|
+
from griptape_nodes.retained_mode.managers.mcp_manager import MCPManager
|
|
65
66
|
from griptape_nodes.retained_mode.managers.model_manager import ModelManager
|
|
66
67
|
from griptape_nodes.retained_mode.managers.node_manager import NodeManager
|
|
67
68
|
from griptape_nodes.retained_mode.managers.object_manager import ObjectManager
|
|
@@ -150,6 +151,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
150
151
|
_version_compatibility_manager: VersionCompatibilityManager
|
|
151
152
|
_session_manager: SessionManager
|
|
152
153
|
_engine_identity_manager: EngineIdentityManager
|
|
154
|
+
_mcp_manager: MCPManager
|
|
153
155
|
_resource_manager: ResourceManager
|
|
154
156
|
_sync_manager: SyncManager
|
|
155
157
|
|
|
@@ -164,6 +166,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
164
166
|
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
165
167
|
from griptape_nodes.retained_mode.managers.flow_manager import FlowManager
|
|
166
168
|
from griptape_nodes.retained_mode.managers.library_manager import LibraryManager
|
|
169
|
+
from griptape_nodes.retained_mode.managers.mcp_manager import MCPManager
|
|
167
170
|
from griptape_nodes.retained_mode.managers.model_manager import ModelManager
|
|
168
171
|
from griptape_nodes.retained_mode.managers.node_manager import NodeManager
|
|
169
172
|
from griptape_nodes.retained_mode.managers.object_manager import ObjectManager
|
|
@@ -212,6 +215,7 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
212
215
|
self._version_compatibility_manager = VersionCompatibilityManager(self._event_manager)
|
|
213
216
|
self._session_manager = SessionManager(self._event_manager)
|
|
214
217
|
self._engine_identity_manager = EngineIdentityManager(self._event_manager)
|
|
218
|
+
self._mcp_manager = MCPManager(self._event_manager, self._config_manager)
|
|
215
219
|
self._sync_manager = SyncManager(self._event_manager, self._config_manager)
|
|
216
220
|
|
|
217
221
|
# Assign handlers now that these are created.
|
|
@@ -374,6 +378,10 @@ class GriptapeNodes(metaclass=SingletonMeta):
|
|
|
374
378
|
def SessionManager(cls) -> SessionManager:
|
|
375
379
|
return GriptapeNodes.get_instance()._session_manager
|
|
376
380
|
|
|
381
|
+
@classmethod
|
|
382
|
+
def MCPManager(cls) -> MCPManager:
|
|
383
|
+
return GriptapeNodes.get_instance()._mcp_manager
|
|
384
|
+
|
|
377
385
|
@classmethod
|
|
378
386
|
def EngineIdentityManager(cls) -> EngineIdentityManager:
|
|
379
387
|
return GriptapeNodes.get_instance()._engine_identity_manager
|
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import threading
|
|
6
6
|
import uuid
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
8
8
|
|
|
9
9
|
from attrs import define, field
|
|
10
10
|
from griptape.artifacts import ErrorArtifact, ImageUrlArtifact, JsonArtifact
|
|
@@ -40,6 +40,10 @@ from griptape_nodes.retained_mode.events.agent_events import (
|
|
|
40
40
|
)
|
|
41
41
|
from griptape_nodes.retained_mode.events.app_events import AppInitializationComplete
|
|
42
42
|
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent, ResultPayload
|
|
43
|
+
from griptape_nodes.retained_mode.events.mcp_events import (
|
|
44
|
+
GetEnabledMCPServersRequest,
|
|
45
|
+
GetEnabledMCPServersResultSuccess,
|
|
46
|
+
)
|
|
43
47
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
44
48
|
from griptape_nodes.retained_mode.managers.config_manager import ConfigManager
|
|
45
49
|
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
@@ -91,6 +95,14 @@ class NodesPromptImageGenerationTool(BaseImageGenerationTool):
|
|
|
91
95
|
|
|
92
96
|
|
|
93
97
|
class AgentManager:
|
|
98
|
+
# Field mappings for each transport type
|
|
99
|
+
TRANSPORT_FIELD_MAPPINGS: ClassVar[dict[str, list[str]]] = {
|
|
100
|
+
"stdio": ["command", "args", "env", "cwd", "encoding", "encoding_error_handler"],
|
|
101
|
+
"sse": ["url", "headers", "timeout", "sse_read_timeout"],
|
|
102
|
+
"streamable_http": ["url", "headers", "timeout", "sse_read_timeout", "terminate_on_close"],
|
|
103
|
+
"websocket": ["url"],
|
|
104
|
+
}
|
|
105
|
+
|
|
94
106
|
def __init__(self, static_files_manager: StaticFilesManager, event_manager: EventManager | None = None) -> None:
|
|
95
107
|
self.conversation_memory = ConversationMemory()
|
|
96
108
|
self.prompt_driver = None
|
|
@@ -135,7 +147,53 @@ class AgentManager:
|
|
|
135
147
|
"transport": "streamable_http",
|
|
136
148
|
"url": f"http://localhost:{GTN_MCP_SERVER_PORT}/mcp/",
|
|
137
149
|
}
|
|
138
|
-
return MCPTool(connection=connection)
|
|
150
|
+
return MCPTool(connection=connection, name="mcpGriptapeNodes")
|
|
151
|
+
|
|
152
|
+
def _create_additional_mcp_tools(self, server_names: list[str]) -> list[MCPTool]:
|
|
153
|
+
"""Create MCP tools for additional servers specified in the request."""
|
|
154
|
+
additional_tools = []
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
app = GriptapeNodes()
|
|
158
|
+
|
|
159
|
+
enabled_request = GetEnabledMCPServersRequest()
|
|
160
|
+
enabled_result = app.handle_request(enabled_request)
|
|
161
|
+
|
|
162
|
+
if not isinstance(enabled_result, GetEnabledMCPServersResultSuccess):
|
|
163
|
+
msg = f"Failed to get enabled MCP servers for additional tools: {enabled_result}. Agent will continue with default MCP tool only."
|
|
164
|
+
logger.warning(msg)
|
|
165
|
+
return additional_tools
|
|
166
|
+
|
|
167
|
+
for server_name in server_names:
|
|
168
|
+
if server_name in enabled_result.servers:
|
|
169
|
+
server_config = enabled_result.servers[server_name]
|
|
170
|
+
connection = self._create_connection_from_mcp_config(server_config) # type: ignore[arg-type]
|
|
171
|
+
tool = MCPTool(connection=connection, name=f"mcp{server_name.title()}") # type: ignore[arg-type]
|
|
172
|
+
additional_tools.append(tool)
|
|
173
|
+
else:
|
|
174
|
+
msg = f"Additional MCP server '{server_name}' not found or not enabled"
|
|
175
|
+
logger.warning(msg)
|
|
176
|
+
|
|
177
|
+
except Exception as e:
|
|
178
|
+
msg = f"Failed to create additional MCP tools: {e}"
|
|
179
|
+
logger.error(msg)
|
|
180
|
+
|
|
181
|
+
return additional_tools
|
|
182
|
+
|
|
183
|
+
def _create_connection_from_mcp_config(self, server_config: dict) -> dict:
|
|
184
|
+
"""Create connection dictionary from MCP server configuration."""
|
|
185
|
+
transport = server_config.get("transport", "stdio")
|
|
186
|
+
|
|
187
|
+
# Start with transport
|
|
188
|
+
connection = {"transport": transport}
|
|
189
|
+
|
|
190
|
+
# Map relevant fields based on transport type
|
|
191
|
+
fields_to_map = self.TRANSPORT_FIELD_MAPPINGS.get(transport, self.TRANSPORT_FIELD_MAPPINGS["stdio"])
|
|
192
|
+
for field_name in fields_to_map:
|
|
193
|
+
if field_name in server_config and server_config[field_name] is not None:
|
|
194
|
+
connection[field_name] = server_config[field_name]
|
|
195
|
+
|
|
196
|
+
return connection
|
|
139
197
|
|
|
140
198
|
async def on_handle_run_agent_request(self, request: RunAgentRequest) -> ResultPayload:
|
|
141
199
|
if self.prompt_driver is None:
|
|
@@ -147,7 +205,7 @@ class AgentManager:
|
|
|
147
205
|
await asyncio.to_thread(self._on_handle_run_agent_request, request)
|
|
148
206
|
return RunAgentResultStarted(result_details="Agent execution started successfully.")
|
|
149
207
|
|
|
150
|
-
def _create_agent(self) -> Agent:
|
|
208
|
+
def _create_agent(self, additional_mcp_servers: list[str] | None = None) -> Agent:
|
|
151
209
|
output_schema = Schema(
|
|
152
210
|
{
|
|
153
211
|
"generated_image_urls": [str],
|
|
@@ -161,6 +219,11 @@ class AgentManager:
|
|
|
161
219
|
if self.mcp_tool is not None:
|
|
162
220
|
tools.append(self.mcp_tool)
|
|
163
221
|
|
|
222
|
+
# Add additional MCP servers if specified
|
|
223
|
+
if additional_mcp_servers:
|
|
224
|
+
additional_tools = self._create_additional_mcp_tools(additional_mcp_servers)
|
|
225
|
+
tools.extend(additional_tools)
|
|
226
|
+
|
|
164
227
|
return Agent(
|
|
165
228
|
prompt_driver=self.prompt_driver,
|
|
166
229
|
conversation_memory=self.conversation_memory,
|
|
@@ -185,7 +248,7 @@ class AgentManager:
|
|
|
185
248
|
for url_artifact in request.url_artifacts
|
|
186
249
|
if url_artifact["type"] == "ImageUrlArtifact"
|
|
187
250
|
]
|
|
188
|
-
agent = self._create_agent()
|
|
251
|
+
agent = self._create_agent(additional_mcp_servers=request.additional_mcp_servers)
|
|
189
252
|
*events, last_event = agent.run_stream([request.input, *artifacts])
|
|
190
253
|
full_result = ""
|
|
191
254
|
last_conversation_output = ""
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import inspect
|
|
5
|
+
import threading
|
|
5
6
|
from collections import defaultdict
|
|
6
7
|
from dataclasses import fields
|
|
7
8
|
from typing import TYPE_CHECKING, Any, cast
|
|
@@ -43,6 +44,10 @@ class EventManager:
|
|
|
43
44
|
self._flush_in_queue: bool = False
|
|
44
45
|
# Event queue for publishing events
|
|
45
46
|
self._event_queue: asyncio.Queue | None = None
|
|
47
|
+
# Keep track of which thread the event loop runs on
|
|
48
|
+
self._loop_thread_id: int | None = None
|
|
49
|
+
# Keep a reference to the event loop for thread-safe operations
|
|
50
|
+
self._event_loop: asyncio.AbstractEventLoop | None = None
|
|
46
51
|
|
|
47
52
|
@property
|
|
48
53
|
def event_queue(self) -> asyncio.Queue:
|
|
@@ -62,23 +67,48 @@ class EventManager:
|
|
|
62
67
|
"""
|
|
63
68
|
if queue is not None:
|
|
64
69
|
self._event_queue = queue
|
|
70
|
+
# Track which thread the event loop is running on and store loop reference
|
|
71
|
+
try:
|
|
72
|
+
self._event_loop = asyncio.get_running_loop()
|
|
73
|
+
self._loop_thread_id = threading.get_ident()
|
|
74
|
+
except RuntimeError:
|
|
75
|
+
self._event_loop = None
|
|
76
|
+
self._loop_thread_id = None
|
|
65
77
|
else:
|
|
66
78
|
try:
|
|
67
79
|
self._event_queue = asyncio.Queue()
|
|
80
|
+
self._event_loop = asyncio.get_running_loop()
|
|
81
|
+
self._loop_thread_id = threading.get_ident()
|
|
68
82
|
except RuntimeError:
|
|
69
83
|
# Defer queue creation until we're in an event loop
|
|
70
84
|
self._event_queue = None
|
|
85
|
+
self._event_loop = None
|
|
86
|
+
self._loop_thread_id = None
|
|
71
87
|
|
|
72
88
|
def put_event(self, event: Any) -> None:
|
|
73
89
|
"""Put event into async queue from sync context (non-blocking).
|
|
74
90
|
|
|
91
|
+
Automatically detects if we're in a different thread and uses thread-safe operations.
|
|
92
|
+
|
|
75
93
|
Args:
|
|
76
94
|
event: The event to publish to the queue
|
|
77
95
|
"""
|
|
78
96
|
if self._event_queue is None:
|
|
79
97
|
return
|
|
80
98
|
|
|
81
|
-
|
|
99
|
+
# Check if we need thread-safe operation
|
|
100
|
+
current_thread_id = threading.get_ident()
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
self._loop_thread_id is not None
|
|
104
|
+
and current_thread_id != self._loop_thread_id
|
|
105
|
+
and self._event_loop is not None
|
|
106
|
+
):
|
|
107
|
+
# We're in a different thread from the event loop, use thread-safe method
|
|
108
|
+
self._event_loop.call_soon_threadsafe(self._event_queue.put_nowait, event)
|
|
109
|
+
else:
|
|
110
|
+
# We're on the same thread as the event loop or no loop thread tracked, use direct method
|
|
111
|
+
self._event_queue.put_nowait(event)
|
|
82
112
|
|
|
83
113
|
async def aput_event(self, event: Any) -> None:
|
|
84
114
|
"""Put event into async queue from async context.
|
|
@@ -91,18 +121,6 @@ class EventManager:
|
|
|
91
121
|
|
|
92
122
|
await self._event_queue.put(event)
|
|
93
123
|
|
|
94
|
-
def put_event_threadsafe(self, loop: Any, event: Any) -> None:
|
|
95
|
-
"""Put event into async queue from sync context in a thread-safe manner.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
loop: The asyncio event loop to use for thread-safe operation
|
|
99
|
-
event: The event to publish to the queue
|
|
100
|
-
"""
|
|
101
|
-
if self._event_queue is None:
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
loop.call_soon_threadsafe(self._event_queue.put_nowait, event)
|
|
105
|
-
|
|
106
124
|
def assign_manager_to_request_type(
|
|
107
125
|
self,
|
|
108
126
|
request_type: type[RP],
|