griptape-nodes 0.68.0__py3-none-any.whl → 0.69.0__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.
@@ -65,8 +65,7 @@ class BaseNodeGroup(BaseNode):
65
65
  Args:
66
66
  nodes: A list of nodes to add to this group
67
67
  """
68
- for node in nodes:
69
- self.nodes[node.name] = node
68
+ self._add_nodes_to_group_dict(nodes)
70
69
 
71
70
  node_names_in_group = set(self.nodes.keys())
72
71
  self.metadata["node_names_in_group"] = list(node_names_in_group)
@@ -93,3 +92,24 @@ class BaseNodeGroup(BaseNode):
93
92
  if node.name not in self.nodes:
94
93
  msg = f"Node {node.name} is not in node group {self.name}"
95
94
  raise ValueError(msg)
95
+
96
+ def handle_child_node_rename(self, old_name: str, new_name: str) -> None:
97
+ """Update group membership when a child node is renamed.
98
+
99
+ Args:
100
+ old_name: The old name of the child node
101
+ new_name: The new name of the child node
102
+ """
103
+ if old_name not in self.nodes:
104
+ return
105
+
106
+ # Update the nodes dictionary
107
+ node = self.nodes.pop(old_name)
108
+ self.nodes[new_name] = node
109
+
110
+ # Update the metadata
111
+ node_names_in_group = self.metadata.get("node_names_in_group", [])
112
+ if old_name in node_names_in_group:
113
+ node_names_in_group.remove(old_name)
114
+ node_names_in_group.append(new_name)
115
+ self.metadata["node_names_in_group"] = node_names_in_group
@@ -1623,7 +1623,7 @@ class EndNode(BaseNode):
1623
1623
  self,
1624
1624
  was_successful_modes={ParameterMode.PROPERTY},
1625
1625
  result_details_modes={ParameterMode.INPUT},
1626
- parameter_group_initially_collapsed=False,
1626
+ parameter_group_initially_collapsed=True,
1627
1627
  result_details_placeholder="Details about the completion or failure will be shown here.",
1628
1628
  )
1629
1629
 
@@ -83,6 +83,17 @@ class ExecutionStatusComponent:
83
83
  """
84
84
  return self._status_group
85
85
 
86
+ def _update_status_group_display_label(self, *, show_warning: bool) -> None:
87
+ """Update the Status ParameterGroup display label with optional warning emoji."""
88
+ if show_warning:
89
+ self._status_group.update_ui_options_key("display_name", "⚠️ Status")
90
+ return
91
+
92
+ # Remove display_name to use default "Status" from name
93
+ ui_options = self._status_group.ui_options.copy()
94
+ ui_options.pop("display_name", None)
95
+ self._status_group.ui_options = ui_options
96
+
86
97
  def set_execution_result(self, *, was_successful: bool, result_details: str) -> None:
87
98
  """Set the execution result values.
88
99
 
@@ -92,6 +103,8 @@ class ExecutionStatusComponent:
92
103
  """
93
104
  self._update_parameter_value(self._was_successful, was_successful)
94
105
  self._update_parameter_value(self._result_details, result_details)
106
+ # Update display label: show warning emoji on failure
107
+ self._update_status_group_display_label(show_warning=not was_successful)
95
108
 
96
109
  def clear_execution_status(self, initial_message: str | None = None) -> None:
97
110
  """Clear execution status and reset parameters.
@@ -102,6 +115,8 @@ class ExecutionStatusComponent:
102
115
  if initial_message is None:
103
116
  initial_message = ""
104
117
  self.set_execution_result(was_successful=False, result_details=initial_message)
118
+ # Reset to "Status" during execution (not a failure yet)
119
+ self._update_status_group_display_label(show_warning=False)
105
120
 
106
121
  def append_to_result_details(self, additional_text: str, separator: str = "\n") -> None:
107
122
  """Append text to the existing result_details.
@@ -3,6 +3,8 @@ from typing import Any
3
3
 
4
4
  from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
5
5
  from griptape_nodes.exe_types.node_types import BaseNode
6
+ from griptape_nodes.exe_types.param_types.parameter_bool import ParameterBool
7
+ from griptape_nodes.exe_types.param_types.parameter_int import ParameterInt
6
8
 
7
9
 
8
10
  class SeedParameter:
@@ -10,24 +12,20 @@ class SeedParameter:
10
12
  self._node = node
11
13
  self._max_seed = max_seed
12
14
 
13
- def add_input_parameters(self) -> None:
14
- self._node.add_parameter(
15
- Parameter(
16
- name="randomize_seed",
17
- type="bool",
18
- output_type="bool",
19
- tooltip="randomize the seed on each run",
20
- default_value=False,
21
- )
15
+ def add_input_parameters(self, *, inside_param_group: bool = False) -> None:
16
+ randomize_seed_parameter = ParameterBool(
17
+ name="randomize_seed",
18
+ tooltip="randomize the seed on each run",
19
+ default_value=False,
22
20
  )
23
- self._node.add_parameter(
24
- Parameter(
25
- name="seed",
26
- type="int",
27
- tooltip="seed",
28
- default_value=42,
29
- )
21
+ seed_parameter = ParameterInt(
22
+ name="seed",
23
+ tooltip="the seed to use for the generation",
24
+ default_value=42,
30
25
  )
26
+ if not inside_param_group:
27
+ self._node.add_parameter(randomize_seed_parameter)
28
+ self._node.add_parameter(seed_parameter)
31
29
 
32
30
  def remove_input_parameters(self) -> None:
33
31
  self._node.remove_parameter_element_by_name("randomize_seed")
@@ -33,6 +33,7 @@ class MCPServerConfig(TypedDict, total=False):
33
33
  terminate_on_close: bool # Session termination behavior for streamable HTTP transport
34
34
  description: str | None
35
35
  capabilities: list[str] | None
36
+ rules: str | None # Optional rules for this MCP server as a single string
36
37
 
37
38
 
38
39
  class MCPServerCapability(TypedDict):
@@ -162,6 +163,7 @@ class CreateMCPServerRequest(RequestPayload):
162
163
  description: Optional description of the server
163
164
  capabilities: List of server capabilities
164
165
  enabled: Whether the server is enabled by default
166
+ rules: Optional rules for this MCP server as a single string
165
167
  """
166
168
 
167
169
  name: str
@@ -186,6 +188,7 @@ class CreateMCPServerRequest(RequestPayload):
186
188
  # Common fields
187
189
  description: str | None = None
188
190
  capabilities: list[str] | None = None
191
+ rules: str | None = None
189
192
 
190
193
 
191
194
  @dataclass
@@ -224,6 +227,7 @@ class UpdateMCPServerRequest(RequestPayload):
224
227
  terminate_on_close: Updated session termination behavior for streamable HTTP transport
225
228
  description: Updated description of the server
226
229
  capabilities: Updated list of server capabilities
230
+ rules: Updated rules for this MCP server as a single string
227
231
  """
228
232
 
229
233
  name: str
@@ -249,6 +253,7 @@ class UpdateMCPServerRequest(RequestPayload):
249
253
  # Common fields
250
254
  description: str | None = None
251
255
  capabilities: list[str] | None = None
256
+ rules: str | None = None
252
257
 
253
258
 
254
259
  @dataclass
@@ -390,8 +390,6 @@ class GetAllNodeInfoResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure)
390
390
  class SetLockNodeStateRequest(WorkflowNotAlteredMixin, RequestPayload):
391
391
  """Lock a node.
392
392
 
393
- Use when: Implementing locking functionality, preventing changes to nodes.
394
-
395
393
  Args:
396
394
  node_name: Name of the node to lock
397
395
  lock: Whether to lock or unlock the node. If true, the node will be locked, otherwise it will be unlocked.
@@ -418,6 +416,39 @@ class SetLockNodeStateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailur
418
416
  """Node failed to lock."""
419
417
 
420
418
 
419
+ @dataclass
420
+ @PayloadRegistry.register
421
+ class BatchSetNodeLockStateRequest(WorkflowNotAlteredMixin, RequestPayload):
422
+ """Set lock state for multiple nodes in a single request.
423
+
424
+ Use when: Locking or unlocking multiple nodes at once, consistent with BatchSetNodeMetadataRequest.
425
+
426
+ Args:
427
+ node_names: Names of nodes to lock/unlock.
428
+ lock: Whether to lock (True) or unlock (False) the nodes.
429
+
430
+ Results: BatchSetNodeLockStateResultSuccess | BatchSetNodeLockStateResultFailure
431
+ """
432
+
433
+ node_names: list[str]
434
+ lock: bool
435
+
436
+
437
+ @dataclass
438
+ @PayloadRegistry.register
439
+ class BatchSetNodeLockStateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
440
+ """Batch node lock state update completed successfully."""
441
+
442
+ updated_nodes: list[str]
443
+ failed_nodes: dict[str, str] = field(default_factory=dict)
444
+
445
+
446
+ @dataclass
447
+ @PayloadRegistry.register
448
+ class BatchSetNodeLockStateResultFailure(ResultPayloadFailure):
449
+ """Batch node lock state update failed. Common causes: all nodes not found."""
450
+
451
+
421
452
  # A Node's state can be serialized to a sequence of commands that the engine runs.
422
453
  @dataclass
423
454
  class SerializedNodeCommands:
@@ -389,49 +389,107 @@ class AgentManager:
389
389
  driver = self.thread_storage_driver.get_conversation_memory_driver(thread_id)
390
390
  conversation_memory = ConversationMemory(conversation_memory_driver=driver)
391
391
 
392
+ # Collect MCP server rulesets
393
+ mcp_rulesets = self._collect_mcp_server_rulesets(additional_mcp_servers)
394
+
395
+ # Get default rulesets
396
+ default_rulesets = self._get_default_rulesets()
397
+
392
398
  return Agent(
393
399
  prompt_driver=self.prompt_driver,
394
400
  conversation_memory=conversation_memory,
395
401
  tools=tools,
396
402
  output_schema=output_schema,
397
- rulesets=[
398
- Ruleset(
399
- name="generated_image_urls",
400
- rules=[
401
- Rule("Do not hallucinate generated_image_urls."),
402
- Rule("Only set generated_image_urls with images generated with your tools."),
403
- ],
404
- ),
405
- # Note: Griptape's MCPTool automatically wraps arguments in a 'values' key, but our MCP server
406
- # expects arguments directly. This ruleset instructs the agent to provide arguments without
407
- # the 'values' wrapper to avoid validation errors. If MCPTool behavior changes in the future,
408
- # this ruleset may need to be updated or removed.
409
- Ruleset(
410
- name="mcp_tool_usage",
411
- rules=[
412
- Rule(
413
- "When calling MCP tools (mcpGriptapeNodes), provide arguments directly without wrapping them in a 'values' key. "
414
- "For example, use {'node_type': 'FluxImageGeneration', 'node_name': 'MyNode'} not {'values': {'node_type': 'FluxImageGeneration'}}."
415
- ),
416
- ],
417
- ),
418
- Ruleset(
419
- name="node_rulesets",
420
- rules=[
421
- Rule(
422
- "When asked to create a node, use ListNodeTypesInLibraryRequest or GetAllInfoForAllLibrariesRequest to check available node types and find the appropriate node."
423
- ),
424
- Rule(
425
- "When matching user requests to node types, account for variations: users may include spaces (e.g., 'Image Generation' vs 'ImageGeneration') or reorder words (e.g., 'Generate Image' vs 'Image Generation'). Match based on the words present, not exact spelling."
426
- ),
427
- Rule(
428
- "If you cannot determine the correct node type or node creation fails, ask the user for clarification."
429
- ),
430
- ],
431
- ),
432
- ],
403
+ rulesets=[*default_rulesets, *mcp_rulesets],
433
404
  )
434
405
 
406
+ @staticmethod
407
+ def _create_ruleset_from_rules_string(rules_string: str | None, server_name: str) -> Ruleset | None:
408
+ """Create a Ruleset from a rules string for an MCP server.
409
+
410
+ Args:
411
+ rules_string: Optional rules string for the MCP server
412
+ server_name: Name of the MCP server
413
+
414
+ Returns:
415
+ Ruleset with a single Rule containing the rules string, or None if rules_string is None/empty
416
+ """
417
+ if not rules_string or not rules_string.strip():
418
+ return None
419
+
420
+ rules_text = rules_string.strip()
421
+ ruleset_name = f"mcp_{server_name}_rules"
422
+
423
+ return Ruleset(name=ruleset_name, rules=[Rule(rules_text)])
424
+
425
+ def _collect_mcp_server_rulesets(self, additional_mcp_servers: list[str] | None) -> list[Ruleset]:
426
+ """Collect rulesets from MCP server configurations."""
427
+ mcp_rulesets = []
428
+
429
+ # Collect server names to get rules for
430
+ server_names_to_check = []
431
+ if additional_mcp_servers:
432
+ server_names_to_check.extend(additional_mcp_servers)
433
+
434
+ # Get rules for all MCP servers
435
+ if not server_names_to_check:
436
+ return mcp_rulesets
437
+
438
+ enabled_result = GriptapeNodes.handle_request(GetEnabledMCPServersRequest())
439
+ if not isinstance(enabled_result, GetEnabledMCPServersResultSuccess):
440
+ return mcp_rulesets
441
+
442
+ for server_name in server_names_to_check:
443
+ if server_name not in enabled_result.servers:
444
+ continue
445
+
446
+ server_config = enabled_result.servers[server_name]
447
+ rules_string = server_config.get("rules")
448
+ ruleset = AgentManager._create_ruleset_from_rules_string(rules_string, server_name)
449
+ if ruleset is not None:
450
+ mcp_rulesets.append(ruleset)
451
+
452
+ return mcp_rulesets
453
+
454
+ def _get_default_rulesets(self) -> list[Ruleset]:
455
+ """Get the default rulesets for agents."""
456
+ return [
457
+ Ruleset(
458
+ name="generated_image_urls",
459
+ rules=[
460
+ Rule("Do not hallucinate generated_image_urls."),
461
+ Rule("Only set generated_image_urls with images generated with your tools."),
462
+ ],
463
+ ),
464
+ # Note: Griptape's MCPTool automatically wraps arguments in a 'values' key, but our MCP server
465
+ # expects arguments directly. This ruleset instructs the agent to provide arguments without
466
+ # the 'values' wrapper to avoid validation errors. If MCPTool behavior changes in the future,
467
+ # this ruleset may need to be updated or removed.
468
+ Ruleset(
469
+ name="mcp_tool_usage",
470
+ rules=[
471
+ Rule(
472
+ "When calling MCP tools (mcpGriptapeNodes), provide arguments directly without wrapping them in a 'values' key. "
473
+ "For example, use {'node_type': 'FluxImageGeneration', 'node_name': 'MyNode'} not {'values': {'node_type': 'FluxImageGeneration'}}."
474
+ ),
475
+ ],
476
+ ),
477
+ Ruleset(
478
+ name="node_rulesets",
479
+ rules=[
480
+ Rule(
481
+ "When asked to create a node, use ListNodeTypesInLibraryRequest or GetAllInfoForAllLibrariesRequest to check available node types and find the appropriate node."
482
+ ),
483
+ Rule(
484
+ "When matching user requests to node types, account for variations: users may include spaces (e.g., 'Image Generation' vs 'ImageGeneration') or reorder words (e.g., 'Generate Image' vs 'Image Generation'). Match based on the words present, not exact spelling."
485
+ ),
486
+ Rule(
487
+ "If you cannot determine the correct node type or node creation fails, ask the user for clarification."
488
+ ),
489
+ ],
490
+ ),
491
+ ]
492
+
435
493
  def _validate_thread_for_run(self, thread_id: str | None) -> str:
436
494
  """Validate and return thread_id for agent run, or raise ValueError."""
437
495
  if thread_id is None:
@@ -510,10 +568,7 @@ class AgentManager:
510
568
  additional_tools = []
511
569
 
512
570
  try:
513
- app = GriptapeNodes()
514
-
515
- enabled_request = GetEnabledMCPServersRequest()
516
- enabled_result = app.handle_request(enabled_request)
571
+ enabled_result = GriptapeNodes.handle_request(GetEnabledMCPServersRequest())
517
572
 
518
573
  if not isinstance(enabled_result, GetEnabledMCPServersResultSuccess):
519
574
  msg = f"Failed to get enabled MCP servers for additional tools: {enabled_result}. Agent will continue with default MCP tool only."
@@ -17,7 +17,7 @@ from griptape_nodes.exe_types.core_types import (
17
17
  ParameterTypeBuiltin,
18
18
  )
19
19
  from griptape_nodes.exe_types.flow import ControlFlow
20
- from griptape_nodes.exe_types.node_groups import SubflowNodeGroup
20
+ from griptape_nodes.exe_types.node_groups import BaseNodeGroup, SubflowNodeGroup
21
21
  from griptape_nodes.exe_types.node_types import (
22
22
  BaseNode,
23
23
  ErrorProxyNode,
@@ -1530,9 +1530,9 @@ class FlowManager:
1530
1530
  if node_name is not None:
1531
1531
  node_name_to_uuid[node_name] = serialize_result.serialized_node_commands.node_uuid
1532
1532
 
1533
- # SubflowNodeGroups must be serialized LAST because they reference child node names via node_names_to_add
1533
+ # BaseNodeGroups must be serialized LAST because they reference child node names via node_names_to_add
1534
1534
  # If we deserialize a NodeGroup before its children, the child nodes won't exist yet
1535
- if isinstance(node, SubflowNodeGroup):
1535
+ if isinstance(node, BaseNodeGroup):
1536
1536
  serialized_node_group_commands.append(serialize_result.serialized_node_commands)
1537
1537
  else:
1538
1538
  serialized_node_commands.append(serialize_result.serialized_node_commands)
@@ -1543,7 +1543,7 @@ class FlowManager:
1543
1543
  serialize_result.set_parameter_value_commands
1544
1544
  )
1545
1545
 
1546
- # Update SubflowNodeGroup commands to use UUIDs instead of names in node_names_to_add
1546
+ # Update BaseNodeGroup commands to use UUIDs instead of names in node_names_to_add
1547
1547
  # This allows workflow generation to directly look up variable names from UUIDs
1548
1548
 
1549
1549
  for node_group_command in serialized_node_group_commands:
@@ -3224,9 +3224,9 @@ class FlowManager:
3224
3224
  # Store the serialized node's UUID for correlation to connections and setting parameter values later.
3225
3225
  node_name_to_uuid[node_name] = serialized_node.node_uuid
3226
3226
 
3227
- # SubflowNodeGroups must be serialized LAST because CreateNodeGroupRequest references child node names
3227
+ # BaseNodeGroups must be serialized LAST because CreateNodeGroupRequest references child node names
3228
3228
  # If we deserialize a NodeGroup before its children, the child nodes won't exist yet
3229
- if isinstance(node, SubflowNodeGroup):
3229
+ if isinstance(node, BaseNodeGroup):
3230
3230
  serialized_node_group_commands.append(serialized_node)
3231
3231
  else:
3232
3232
  serialized_node_commands.append(serialized_node)
@@ -3299,7 +3299,7 @@ class FlowManager:
3299
3299
  # This ensures child nodes exist before their parent NodeGroups are created during deserialization
3300
3300
  serialized_node_commands.extend(serialized_node_group_commands)
3301
3301
 
3302
- # Update SubflowNodeGroup commands to use UUIDs instead of names in node_names_to_add
3302
+ # Update BaseNodeGroup commands to use UUIDs instead of names in node_names_to_add
3303
3303
  # This allows workflow generation to directly look up variable names from UUIDs
3304
3304
  # Build a complete node name to UUID map including nodes from all subflows
3305
3305
  complete_node_name_to_uuid = dict(node_name_to_uuid) # Start with current flow's nodes
@@ -127,6 +127,7 @@ class MCPManager:
127
127
  "terminate_on_close": "terminate_on_close",
128
128
  "description": "description",
129
129
  "capabilities": "capabilities",
130
+ "rules": "rules",
130
131
  }
131
132
 
132
133
  # Update fields that are not None
@@ -213,6 +214,7 @@ class MCPManager:
213
214
  # Common fields
214
215
  description=request.description,
215
216
  capabilities=request.capabilities or [],
217
+ rules=request.rules,
216
218
  )
217
219
 
218
220
  servers.append(server_config)
@@ -70,6 +70,9 @@ from griptape_nodes.retained_mode.events.node_events import (
70
70
  AddNodesToNodeGroupRequest,
71
71
  AddNodesToNodeGroupResultFailure,
72
72
  AddNodesToNodeGroupResultSuccess,
73
+ BatchSetNodeLockStateRequest,
74
+ BatchSetNodeLockStateResultFailure,
75
+ BatchSetNodeLockStateResultSuccess,
73
76
  BatchSetNodeMetadataRequest,
74
77
  BatchSetNodeMetadataResultFailure,
75
78
  BatchSetNodeMetadataResultSuccess,
@@ -292,6 +295,9 @@ class NodeManager:
292
295
  CanResetNodeToDefaultsRequest, self.on_can_reset_node_to_defaults_request
293
296
  )
294
297
  event_manager.assign_manager_to_request_type(ResetNodeToDefaultsRequest, self.on_reset_node_to_defaults_request)
298
+ event_manager.assign_manager_to_request_type(
299
+ BatchSetNodeLockStateRequest, self.on_batch_set_lock_node_state_request
300
+ )
295
301
 
296
302
  def handle_node_rename(self, old_name: str, new_name: str) -> None:
297
303
  # Get the node itself
@@ -317,6 +323,12 @@ class NodeManager:
317
323
  connection.source_node.name = new_name
318
324
  temp = connections.outgoing_index.pop(old_name)
319
325
  connections.outgoing_index[new_name] = temp
326
+
327
+ # Update parent group membership if node belongs to a group
328
+ parent_group = node.parent_group
329
+ if parent_group is not None and isinstance(parent_group, BaseNodeGroup):
330
+ parent_group.handle_child_node_rename(old_name, new_name)
331
+
320
332
  # update the node in the flow!
321
333
  flow.remove_node(old_name)
322
334
  node.name = new_name
@@ -2631,9 +2643,8 @@ class NodeManager:
2631
2643
  library_version = library_metadata_result.metadata.library_version
2632
2644
  library_details = LibraryNameAndVersion(library_name=library_used, library_version=library_version)
2633
2645
 
2634
- # Handle SubflowNodeGroup specially - serialize like normal nodes but preserve node group behavior
2635
- if isinstance(node, SubflowNodeGroup):
2636
- # For non-SubflowNodeGroup, library_details should always be set
2646
+ # Handle BaseNodeGroup specially - serialize like normal nodes but preserve node group behavior
2647
+ if isinstance(node, BaseNodeGroup):
2637
2648
  if library_details is None:
2638
2649
  details = f"Attempted to serialize Node '{node_name}' to commands. Library details missing."
2639
2650
  return SerializeNodeToCommandsResultFailure(result_details=details)
@@ -2651,7 +2662,6 @@ class NodeManager:
2651
2662
  metadata=metadata_copy,
2652
2663
  )
2653
2664
  else:
2654
- # For non-SubflowNodeGroup, library_details should always be set
2655
2665
  if library_details is None:
2656
2666
  details = f"Attempted to serialize Node '{node_name}' to commands. Library details missing."
2657
2667
  return SerializeNodeToCommandsResultFailure(result_details=details)
@@ -3588,6 +3598,30 @@ class NodeManager:
3588
3598
  result_details=f"Successfully set lock state to {node.lock} for node '{node_name}'.",
3589
3599
  )
3590
3600
 
3601
+ def on_batch_set_lock_node_state_request(self, request: BatchSetNodeLockStateRequest) -> ResultPayload:
3602
+ updated: list[str] = []
3603
+ failed: dict[str, str] = {}
3604
+ for name in request.node_names:
3605
+ try:
3606
+ node = self.get_node_by_name(name)
3607
+ except ValueError as err:
3608
+ failed[name] = f"Node not found. Error: {err}"
3609
+ continue
3610
+ node.lock = request.lock
3611
+ updated.append(name)
3612
+
3613
+ if not updated:
3614
+ details = f"Failed to update any nodes. Failed: {failed}"
3615
+ return BatchSetNodeLockStateResultFailure(result_details=details)
3616
+ details = f"Successfully set lock state to {request.lock} for nodes: {', '.join(updated)}." + (
3617
+ f" Failed: {failed}" if failed else ""
3618
+ )
3619
+ return BatchSetNodeLockStateResultSuccess(
3620
+ updated_nodes=updated,
3621
+ failed_nodes=failed,
3622
+ result_details=details,
3623
+ )
3624
+
3591
3625
  def on_send_node_message_request(self, request: SendNodeMessageRequest) -> ResultPayload:
3592
3626
  """Handle a SendNodeMessageRequest by calling the node's message callback.
3593
3627
 
@@ -97,6 +97,7 @@ class MCPServerConfig(BaseModel):
97
97
  # Common fields
98
98
  description: str | None = Field(default=None, description="Optional description of what this MCP server provides")
99
99
  capabilities: list[str] = Field(default_factory=list, description="List of capabilities this MCP server provides")
100
+ rules: str | None = Field(default=None, description="Optional rules for this MCP server as a single string.")
100
101
 
101
102
  def __str__(self) -> str:
102
103
  return f"{self.name} ({'enabled' if self.enabled else 'disabled'})"
@@ -44,6 +44,9 @@ from griptape_nodes.retained_mode.events.library_events import (
44
44
  ListRegisteredLibrariesRequest,
45
45
  )
46
46
  from griptape_nodes.retained_mode.events.node_events import (
47
+ BatchSetNodeLockStateRequest,
48
+ BatchSetNodeLockStateResultFailure,
49
+ BatchSetNodeLockStateResultSuccess,
47
50
  CreateNodeRequest,
48
51
  CreateNodeResultFailure,
49
52
  DeleteNodeRequest,
@@ -388,6 +391,20 @@ class RetainedMode:
388
391
  result = GriptapeNodes().handle_request(request)
389
392
  return result
390
393
 
394
+ @classmethod
395
+ def batch_set_lock_node_state(
396
+ cls, *, node_names: list[str], lock: bool = True
397
+ ) -> BatchSetNodeLockStateResultSuccess | BatchSetNodeLockStateResultFailure:
398
+ """Sets the lock state of multiple nodes.
399
+
400
+ Args:
401
+ node_names (list[str]): Names of nodes to lock/unlock.
402
+ lock (bool): Whether to lock (True) or unlock (False) the nodes.
403
+ """
404
+ request = BatchSetNodeLockStateRequest(node_names=node_names, lock=lock)
405
+ result = GriptapeNodes().handle_request(request)
406
+ return result
407
+
391
408
  @classmethod
392
409
  def get_connections_for_node(cls, node_name: str) -> ResultPayload:
393
410
  """Gets all connections associated with a node.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: griptape-nodes
3
- Version: 0.68.0
3
+ Version: 0.69.0
4
4
  Summary: Add your description here
5
- Requires-Dist: griptape>=1.8.12
5
+ Requires-Dist: griptape>=1.9.0
6
6
  Requires-Dist: pydantic>=2.10.6
7
7
  Requires-Dist: python-dotenv>=1.0.1
8
8
  Requires-Dist: xdg-base-dirs>=6.0.2
9
9
  Requires-Dist: httpx>=0.28.0,<1.0.0
10
- Requires-Dist: websockets>=15.0.1,<16.0.0
10
+ Requires-Dist: websockets>=15.0.1,<17.0.0
11
11
  Requires-Dist: tomlkit>=0.13.2
12
12
  Requires-Dist: uv>=0.6.16
13
13
  Requires-Dist: fastapi>=0.115.12
@@ -69,15 +69,15 @@ griptape_nodes/exe_types/core_types.py,sha256=XrMT0Rf0bPZBcx06f1GUTi5G_IwLgdyzGj
69
69
  griptape_nodes/exe_types/flow.py,sha256=2iAh3vN5wnMVxTc5jcPBg9TSiASq1DGIm5jgpO9Bdq4,5732
70
70
  griptape_nodes/exe_types/node_groups/__init__.py,sha256=u91XCSR4OAAr6x5kYq8i14mtHEWGQF_fSDUsWHmiOdY,346
71
71
  griptape_nodes/exe_types/node_groups/base_iterative_node_group.py,sha256=sPPy3Psl-Ku_R46upWm_8hIGFLNPeLpUBN_Sm4RzXzg,9655
72
- griptape_nodes/exe_types/node_groups/base_node_group.py,sha256=dJofcmERzYQSQ2KB7dC0rOKjbZKW8Ko8eD6r0smBu48,3209
72
+ griptape_nodes/exe_types/node_groups/base_node_group.py,sha256=loDdJKllRPpWSSk-jJpqbBto80yn5oZGc6Jl-VXa_8A,3955
73
73
  griptape_nodes/exe_types/node_groups/subflow_node_group.py,sha256=UlZS95TKUCWiHgg6z4Wm1t9xXfUjWhk0L8pB5cXMcV0,48361
74
- griptape_nodes/exe_types/node_types.py,sha256=pPljG84zJ6Wd6yzcRjnK0n1805b-Jyzxx7-NFox66QE,84227
74
+ griptape_nodes/exe_types/node_types.py,sha256=Hrzun9DN4FA-Yk-YQXBFyGrH1-kzszd9eVq4y6rmV5Y,84226
75
75
  griptape_nodes/exe_types/param_components/README.md,sha256=mcNFxJIan9CGTnecsrJ8mkHC55dlA3fb7k4HFznou-k,14850
76
76
  griptape_nodes/exe_types/param_components/__init__.py,sha256=ocm75WnsgiD6ozKVGFhoH9cQe_FEzeF2osxrRujOes0,60
77
77
  griptape_nodes/exe_types/param_components/api_key_provider_parameter.py,sha256=MHn5zYb2vEf4bGBfbnTjCfVYEbpZtXThb8JJAAMpuR8,7766
78
78
  griptape_nodes/exe_types/param_components/artifact_url/__init__.py,sha256=LKAGdP8VBSOTx8iq8kEvxZVDgMIS5TXSLntZqBEJ7yk,40
79
79
  griptape_nodes/exe_types/param_components/artifact_url/public_artifact_url_parameter.py,sha256=2A4Jn7Xgi6KPM087wnVhoH3k5GnqxLFt3MV56SwYIYI,6610
80
- griptape_nodes/exe_types/param_components/execution_status_component.py,sha256=eeUcUOOraGa6sDH_zg_QnGPoP0K-pm3fu90-KAtrmq8,5532
80
+ griptape_nodes/exe_types/param_components/execution_status_component.py,sha256=QYFxYlCaNhllo_iROpyGDEiPb0w9ddYKplDh8F27lgM,6329
81
81
  griptape_nodes/exe_types/param_components/huggingface/__init__.py,sha256=YeVFck-9C341Bt7TiWEvPdD63o9qkaNu2KjmzT9CQOI,39
82
82
  griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py,sha256=rEGN4kvblSAlRh7pD73U__wzAtVzDYWYlDC-k1nKtyM,6395
83
83
  griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py,sha256=c61de3mbkc5FvVkf5vubtD0XfThDInTfibAtYhZKcKA,4265
@@ -86,7 +86,7 @@ griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_variant_p
86
86
  griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py,sha256=cE0Ht81kDcZjlN_2VoRirCA4zMlrG9GFlcptn-gC2tU,4696
87
87
  griptape_nodes/exe_types/param_components/log_parameter.py,sha256=K6XRWVIpL9MtQ6IZG3hYYwIv3sNqFxONxbVnKcA_Oes,4160
88
88
  griptape_nodes/exe_types/param_components/progress_bar_component.py,sha256=GrAFTOrLNAx6q9zfOqiioPTG_NLHKhTe9NiZOo6zGSc,1949
89
- griptape_nodes/exe_types/param_components/seed_parameter.py,sha256=DUaYJTU8Jafu5sfibKJiO628813C37zMIJLYeXcoeuE,2119
89
+ griptape_nodes/exe_types/param_components/seed_parameter.py,sha256=I48cVAojrD5oX4CT7v0x9LX6MrKVklywkp8IjE5pGIo,2321
90
90
  griptape_nodes/exe_types/param_types/__init__.py,sha256=xEEmKvIFF6M7zVjQZCupbbv6SZKt-itD-rPtfRhxJVg,53
91
91
  griptape_nodes/exe_types/param_types/parameter_audio.py,sha256=9oIBtDg6miZDD_5y8VWr6XXjYhsRb9HjSKoaLdgqaHA,9534
92
92
  griptape_nodes/exe_types/param_types/parameter_bool.py,sha256=VS76CODOy7_1MJI9spxmi9VwL3fwCxRKaj_8zpqB7dw,8215
@@ -128,9 +128,9 @@ griptape_nodes/retained_mode/events/flow_events.py,sha256=UcRHfaubb1GpDFSPg-5HRW
128
128
  griptape_nodes/retained_mode/events/generate_request_payload_schemas.py,sha256=PQ0QRZAUBljSIGjB8QhYUeRmSrfmLsJ6XVcygJKi13I,1102
129
129
  griptape_nodes/retained_mode/events/library_events.py,sha256=4NUc3uoLgofo1X9nrCVl2zPX3hsnk06Oo4Op0AQKA5o,36229
130
130
  griptape_nodes/retained_mode/events/logger_events.py,sha256=jYlxzPomgCsJuPtJ0znWBhD8QJfC8qC4xfChDiuVuyg,705
131
- griptape_nodes/retained_mode/events/mcp_events.py,sha256=51OlMZVcabdcIxBb_ojvrHtq2jAzZA31pooPBKaApyg,10734
131
+ griptape_nodes/retained_mode/events/mcp_events.py,sha256=fs83jAQamhwNC9Zt8UNkBKd6iTids8531V_RMnGj__o,11008
132
132
  griptape_nodes/retained_mode/events/model_events.py,sha256=jXm-v-_Uxysn4MJ7a80_uIphrxTHgua3BGgHGyy3T_Y,9232
133
- griptape_nodes/retained_mode/events/node_events.py,sha256=vpILjBkgrPzF_KSow2EJCwcU-tDD78WEWM2DAAzhhiE,37720
133
+ griptape_nodes/retained_mode/events/node_events.py,sha256=JZ-fs1nLV7MCiJzIkuA79rIFDgOAkzV0w5UXGCuchSE,38657
134
134
  griptape_nodes/retained_mode/events/object_events.py,sha256=cJaqEU73Lzf1RRxJrFqEpl8eTr-gDhKpXKywJ-vVCJQ,2631
135
135
  griptape_nodes/retained_mode/events/os_events.py,sha256=fVraPXCz48kDpCTttJFtsyfTfkaLSfTX57hexYSD7I8,23429
136
136
  griptape_nodes/retained_mode/events/parameter_events.py,sha256=TazVXxvfv64n3XJzToOQKiWQ1UIzrUAB4ri_QIvZ2ng,26968
@@ -145,7 +145,7 @@ griptape_nodes/retained_mode/events/variable_events.py,sha256=fnl_sY8GWI_R4UJQTE
145
145
  griptape_nodes/retained_mode/events/workflow_events.py,sha256=TFJBYAERYZ6gLGGkLFUNxEkPP0uub7RXubPjvgy0ark,24915
146
146
  griptape_nodes/retained_mode/griptape_nodes.py,sha256=0pv6TmHqcaNPb1Rd4XJzhYMLtxrl7eLQLpZsDSvrhz0,20178
147
147
  griptape_nodes/retained_mode/managers/__init__.py,sha256=OTXysKusqYCQeAYwnVj4PbE3MxvAUTq9xOZT6vUE3JA,24
148
- griptape_nodes/retained_mode/managers/agent_manager.py,sha256=B7O9lczy83AJjRwdHUFgs6I0Pd3ANNbzPYhf70bsg7c,26311
148
+ griptape_nodes/retained_mode/managers/agent_manager.py,sha256=FOKrcHEBw6M-RmfWTWEQmV-9iy9cD1zHiiDUDMqJHE4,28307
149
149
  griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py,sha256=LyWzHQKmyDh-2zh2PIahdYyAthf_rMYs4zvhtVo-LsU,3423
150
150
  griptape_nodes/retained_mode/managers/config_manager.py,sha256=74AMeaCS0eOeFbzMk5I54MtB7-SNQbwlx_-O6zBBQRo,26739
151
151
  griptape_nodes/retained_mode/managers/context_manager.py,sha256=eb44_CAZhCg2iYIoodlAPpYc67tG3sHyq9dPNoiq_1s,23031
@@ -201,11 +201,11 @@ griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_f
201
201
  griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py,sha256=kPhaIeDxp0duxJBJLKFiYp269kEDsCx3uVLnkKbF2gk,895
202
202
  griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py,sha256=NLAoF_x7g5tL70qZtPVFI8xeM1jPZNsm7FKt9nkuGBc,542
203
203
  griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py,sha256=rUnbBBnGwgq5WTVPt2G4pGP3gVPoX5xvCdTkRPBmP04,1328
204
- griptape_nodes/retained_mode/managers/flow_manager.py,sha256=fY0xCXHiualDwVQJnNlPpMUnLz5bYWvr5D1_UvOyNVM,220269
204
+ griptape_nodes/retained_mode/managers/flow_manager.py,sha256=BKWLpc-udlZ4n1rlKINHFS-Jj6wBqoCSdD7x0FyNr5o,220266
205
205
  griptape_nodes/retained_mode/managers/library_manager.py,sha256=paogGavZj2MDOYvfUklhjORQg16YdpVGBxBncVSxtR0,192265
206
- griptape_nodes/retained_mode/managers/mcp_manager.py,sha256=Ezmn5_48r4JWYe-tFGqmw9dXLvvTiO1rw9b4MNCkw0U,15955
206
+ griptape_nodes/retained_mode/managers/mcp_manager.py,sha256=AEHG1SqFc3g5hOImOHaH7ZjOkmgNAavmQgG9osXVGkE,16018
207
207
  griptape_nodes/retained_mode/managers/model_manager.py,sha256=3lj2X8vIvDSERPtR2VEXNFEWy_D8H6muxRvD-PEx8U8,44845
208
- griptape_nodes/retained_mode/managers/node_manager.py,sha256=KbThJ5fvBTuF9EEIqBT37Rop41_7o-irb_-KY8hkXVY,225038
208
+ griptape_nodes/retained_mode/managers/node_manager.py,sha256=Nvbq3P5Q7hwDMXJDztlZuso7v-K0IOqwoBOUCupZ8Nw,226378
209
209
  griptape_nodes/retained_mode/managers/object_manager.py,sha256=xABsjpE1_HLEIFwYj60zDOLi18EqrYUBuNaj37lUcH4,12921
210
210
  griptape_nodes/retained_mode/managers/operation_manager.py,sha256=4Vn_79vHrawy3wJVUx52tfblO4mURww58nb5RtCTpKU,20190
211
211
  griptape_nodes/retained_mode/managers/os_manager.py,sha256=Ttgj6R063p2Yf1lcdeSTB2xOTg94MIDCMPxO8FDczEQ,142397
@@ -222,14 +222,14 @@ griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py,sha256=YHjU
222
222
  griptape_nodes/retained_mode/managers/resource_types/os_resource.py,sha256=HHJ-kgZB2BsnvU0rp-CQlM1xTJm3OZ3Dh-NpB01gGG8,3439
223
223
  griptape_nodes/retained_mode/managers/secrets_manager.py,sha256=knoHlbR-VrkeNULYjq72cHnhbCeupkKjtUT0EfBH8LQ,7743
224
224
  griptape_nodes/retained_mode/managers/session_manager.py,sha256=u3OEir9rjwG7GFM4MA5crKwcwq6F-lCUKlFzxlFU4co,14085
225
- griptape_nodes/retained_mode/managers/settings.py,sha256=BnIOYxsaE834_oIg6C_oRqFz0MDv8qSqLtwEGpiJZvA,12054
225
+ griptape_nodes/retained_mode/managers/settings.py,sha256=UxYA5GzHqBPFMNwKqq2Np4nT8ZciBahpDIFrhQKNMm0,12168
226
226
  griptape_nodes/retained_mode/managers/static_files_manager.py,sha256=rWeI5mO8CCdEHx1odo4z4pxfTy33ggzCcUwkRj67aNM,12408
227
227
  griptape_nodes/retained_mode/managers/sync_manager.py,sha256=dF6nAAy1DLDQydefx7v3EtPCBiCrk4vURMum57WfmK8,21448
228
228
  griptape_nodes/retained_mode/managers/user_manager.py,sha256=JAOOKDhUfIbj0CJ5EHRBcplmxheQTzeogbvGO23VqXQ,4508
229
229
  griptape_nodes/retained_mode/managers/variable_manager.py,sha256=TnuqHSRK9Yiu_EtKxQksF9SyyQb72lbFQuTQZdpBxeE,24116
230
230
  griptape_nodes/retained_mode/managers/version_compatibility_manager.py,sha256=kaCtmNMqJW-mvLCFHf77wbBZbc3CgW2h8fYYkLkNrPs,17041
231
231
  griptape_nodes/retained_mode/managers/workflow_manager.py,sha256=n6jmr966Y_0MPd9aaOssX2CYgojf_mNRxC07990Anec,217769
232
- griptape_nodes/retained_mode/retained_mode.py,sha256=dG8FFpLXSxT49naOcphEq2aSRCEVoQyklWgaDljjuDA,71728
232
+ griptape_nodes/retained_mode/retained_mode.py,sha256=fJNAVyheqjXFOh8--VAmJBLWlNgMyRWNghG_0u8B-1w,72415
233
233
  griptape_nodes/retained_mode/utils/__init__.py,sha256=W5dvv8YwvVVq_8eVTgMd3Z_VB_Dtq1sIIVq8745QH_I,52
234
234
  griptape_nodes/retained_mode/utils/name_generator.py,sha256=IZLahtfP3XC79XApLdGoZ0IKKUkgiITpd16RK7NbyEs,2524
235
235
  griptape_nodes/retained_mode/variable_types.py,sha256=GVrSWMB3gEDAufSPOBXbNfIRhA9M43MoxpqLyuIg_HE,435
@@ -276,7 +276,7 @@ griptape_nodes/version_compatibility/versions/v0_65_5/__init__.py,sha256=4UyspOV
276
276
  griptape_nodes/version_compatibility/versions/v0_65_5/flux_2_removed_parameters.py,sha256=jOlmY5kKHXh8HPzAUtvwMJqzD4bP7pkE--yHUb7jTRA,3305
277
277
  griptape_nodes/version_compatibility/versions/v0_7_0/__init__.py,sha256=IzPPmGK86h2swfGGTOHyVcBIlOng6SjgWQzlbf3ngmo,51
278
278
  griptape_nodes/version_compatibility/versions/v0_7_0/local_executor_argument_addition.py,sha256=Thx8acnbw5OychhwEEj9aFxvbPe7Wgn4V9ZmZ7KRZqc,2082
279
- griptape_nodes-0.68.0.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
280
- griptape_nodes-0.68.0.dist-info/entry_points.txt,sha256=qvevqd3BVbAV5TcantnAm0ouqaqYKhsRO3pkFymWLWM,82
281
- griptape_nodes-0.68.0.dist-info/METADATA,sha256=N19_zdNWANm20AwShxLGrCdlns7RVwaJ9IoewOOXH1Q,5374
282
- griptape_nodes-0.68.0.dist-info/RECORD,,
279
+ griptape_nodes-0.69.0.dist-info/WHEEL,sha256=XjEbIc5-wIORjWaafhI6vBtlxDBp7S9KiujWF1EM7Ak,79
280
+ griptape_nodes-0.69.0.dist-info/entry_points.txt,sha256=qvevqd3BVbAV5TcantnAm0ouqaqYKhsRO3pkFymWLWM,82
281
+ griptape_nodes-0.69.0.dist-info/METADATA,sha256=jtyansag6yOGTs1B9zyWv7NiBqp_bBl8UGXjedvRLjs,5373
282
+ griptape_nodes-0.69.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.24
2
+ Generator: uv 0.9.25
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any