Tools¶
The ToolNode for wrapping external MCP/UTCP tools, its configuration models,
OAuth support, connection and artifact-extraction presets, and the typed tool
error hierarchy.
tools
¶
ToolNode package exports.
POPULAR_MCP_SERVERS
module-attribute
¶
POPULAR_MCP_SERVERS = {'github': ExternalToolConfig(name='github', transport=TransportType.MCP, connection='npx -y @modelcontextprotocol/server-github', auth_type=AuthType.OAUTH2_USER, description='GitHub repositories, issues, pull requests'), 'filesystem': ExternalToolConfig(name='filesystem', transport=TransportType.MCP, connection='npx -y @modelcontextprotocol/server-filesystem /data', auth_type=AuthType.NONE, description='Read/write local filesystem'), 'postgres': ExternalToolConfig(name='postgres', transport=TransportType.MCP, connection='npx -y @modelcontextprotocol/server-postgres', env={'DATABASE_URL': '${DATABASE_URL}'}, auth_type=AuthType.NONE, description='Query PostgreSQL databases'), 'slack': ExternalToolConfig(name='slack', transport=TransportType.MCP, connection='npx -y @modelcontextprotocol/server-slack', auth_type=AuthType.OAUTH2_USER, description='Slack channels, messages, users'), 'google-drive': ExternalToolConfig(name='google-drive', transport=TransportType.MCP, connection='npx -y @anthropic/mcp-server-google-drive', auth_type=AuthType.OAUTH2_USER, description='Google Drive files and folders'), 'duckduckgo': ExternalToolConfig(name='duckduckgo', transport=TransportType.MCP, connection='npx -y duckduckgo-mcp-server', auth_type=AuthType.NONE, description='Web search and content retrieval via DuckDuckGo'), 'brave-search': ExternalToolConfig(name='brave-search', transport=TransportType.MCP, connection='npx -y @anthropic/mcp-server-brave-search', env={'BRAVE_API_KEY': '${BRAVE_API_KEY}'}, auth_type=AuthType.NONE, description='Web search via Brave Search API (requires BRAVE_API_KEY env var)'), 'redis': ExternalToolConfig(name='redis', transport=TransportType.MCP, connection='uvx --from redis-mcp-server@latest redis-mcp-server', env={'REDIS_HOST': '${REDIS_HOST}', 'REDIS_PORT': '${REDIS_PORT}'}, auth_type=AuthType.NONE, description='Redis database operations (requires uvx/Python)')}
ARTIFACT_PRESETS
module-attribute
¶
ARTIFACT_PRESETS: dict[str, ArtifactExtractionConfig] = {'tableau': TABLEAU_ARTIFACT_PRESET, 'github': GITHUB_ARTIFACT_PRESET, 'filesystem': FILESYSTEM_ARTIFACT_PRESET, 'google-drive': GOOGLE_DRIVE_ARTIFACT_PRESET}
FILESYSTEM_ARTIFACT_PRESET
module-attribute
¶
FILESYSTEM_ARTIFACT_PRESET = ArtifactExtractionConfig(max_inline_size=50000, auto_artifact_large_content=True, binary_detection=BinaryDetectionConfig(enabled=True, min_size_for_detection=500, require_magic_bytes=True), resources=ResourceHandlingConfig(enabled=True, auto_read_if_size_under_bytes=100000, inline_text_if_under_chars=50000, cache_reads_to_artifacts=True), tool_fields={'read_file': [ArtifactFieldConfig(field_path='content', content_type='binary', mime_type=None, summary_template="Read file '{path}' ({size} bytes). Artifact ID: {artifact_id}")]}, default_binary_summary='File content stored as artifact ({mime_type}, {size} bytes). Artifact ID: {artifact_id}', default_text_summary='Large text file stored as artifact ({size} chars). Artifact ID: {artifact_id}')
GITHUB_ARTIFACT_PRESET
module-attribute
¶
GITHUB_ARTIFACT_PRESET = ArtifactExtractionConfig(max_inline_size=10000, auto_artifact_large_content=True, binary_detection=BinaryDetectionConfig(enabled=True, min_size_for_detection=1000, require_magic_bytes=True), resources=ResourceHandlingConfig(enabled=True, auto_read_if_size_under_bytes=50000, inline_text_if_under_chars=10000, cache_reads_to_artifacts=True), tool_fields={'get_file_contents': [ArtifactFieldConfig(field_path='content', content_type='binary', mime_type=None, summary_template="Retrieved file '{path}' ({size} bytes). Artifact ID: {artifact_id}")], 'download_artifact': [ArtifactFieldConfig(field_path='data', content_type='binary', mime_type='application/zip', summary_template="Downloaded artifact '{name}' ({size} bytes). Artifact ID: {artifact_id}")], 'get_release_asset': [ArtifactFieldConfig(field_path='content', content_type='binary', mime_type=None, summary_template="Downloaded release asset '{name}' ({size} bytes). Artifact ID: {artifact_id}")]}, default_binary_summary='GitHub content stored as artifact ({mime_type}, {size} bytes). Artifact ID: {artifact_id}')
GOOGLE_DRIVE_ARTIFACT_PRESET
module-attribute
¶
GOOGLE_DRIVE_ARTIFACT_PRESET = ArtifactExtractionConfig(max_inline_size=10000, auto_artifact_large_content=True, binary_detection=BinaryDetectionConfig(enabled=True, min_size_for_detection=1000, require_magic_bytes=True), resources=ResourceHandlingConfig(enabled=True, auto_read_if_size_under_bytes=100000, inline_text_if_under_chars=10000, cache_reads_to_artifacts=True), tool_fields={'download_file': [ArtifactFieldConfig(field_path='content', content_type='binary', mime_type=None, summary_template="Downloaded '{name}' from Google Drive ({size} bytes). Artifact ID: {artifact_id}")], 'export_document': [ArtifactFieldConfig(field_path='content', content_type='pdf', mime_type='application/pdf', summary_template="Exported document '{name}' as PDF ({size} bytes). Artifact ID: {artifact_id}")]}, default_binary_summary='Google Drive content stored as artifact ({mime_type}, {size} bytes). Artifact ID: {artifact_id}')
TABLEAU_ARTIFACT_PRESET
module-attribute
¶
TABLEAU_ARTIFACT_PRESET = ArtifactExtractionConfig(max_inline_size=5000, auto_artifact_large_content=True, binary_detection=BinaryDetectionConfig(enabled=True, min_size_for_detection=500, require_magic_bytes=True), resources=ResourceHandlingConfig(enabled=True, auto_read_if_size_under_bytes=0, inline_text_if_under_chars=5000, cache_reads_to_artifacts=True), tool_fields={'download_workbook': [ArtifactFieldConfig(field_path='content', content_type='pdf', mime_type='application/pdf', summary_template="Downloaded workbook '{name}' as PDF ({size} bytes). Artifact ID: {artifact_id}")], 'get_view_as_pdf': [ArtifactFieldConfig(field_path='pdf_data', content_type='pdf', mime_type='application/pdf', summary_template="Exported view '{view_name}' as PDF ({size} bytes). Artifact ID: {artifact_id}")], 'get_view_as_image': [ArtifactFieldConfig(field_path='image_data', content_type='image', mime_type='image/png', summary_template="Exported view '{view_name}' as image ({size} bytes). Artifact ID: {artifact_id}")], 'export_dashboard': [ArtifactFieldConfig(field_path='content', content_type='pdf', mime_type='application/pdf', summary_template="Exported dashboard '{name}' ({size} bytes). Artifact ID: {artifact_id}")]}, default_binary_summary='Tableau content stored as artifact ({mime_type}, {size} bytes). Artifact ID: {artifact_id}')
ToolNode
dataclass
¶
ToolNode(config: ExternalToolConfig, registry: ModelRegistry, auth_manager: Any | None = None, _mcp_client: Any | None = None, _utcp_client: Any | None = None, _tools: list[NodeSpec] = list(), _tool_name_map: dict[str, str] = dict(), _connected: bool = False, _connected_loop: Any | None = None, _resource_cache: ResourceCache | None = None, _subscription_manager: ResourceSubscriptionManager | None = None, _resources: list[ResourceInfo] = list(), _resource_templates: list[ResourceTemplateInfo] = list(), _resources_supported: bool = False, _resource_update_callback: Callable[[str], None] | None = None, _prompts: list[PromptInfo] = list(), _prompts_supported: bool = False, _prompts_stale: bool = False, _app_metadata: dict[str, AppMetadata] = dict())
Unified external tool integration for Penguiflow (MCP only for Phase 1).
ToolNode connects to a single external tool source (an MCP server, or a
UTCP/HTTP/CLI endpoint) and converts whatever tools/resources/prompts it
exposes into Penguiflow NodeSpec objects that the planner and registry
can consume. Callers typically construct one ToolNode per configured
source, await connect() it, register get_tools() with the planner,
invoke tools through call(), and await close() it during shutdown.
Attributes:
| Name | Type | Description |
|---|---|---|
config |
ExternalToolConfig
|
Static configuration for the tool source (transport, auth,
retry policy, artifact-extraction rules, etc.). See
:class: |
registry |
ModelRegistry
|
The shared :class: |
auth_manager |
Any | None
|
Optional OAuth manager used to resolve and refresh user
tokens for |
Example
node = ToolNode(config=cfg, registry=registry) await node.connect() specs = node.get_tools() await node.close()
resources_supported
property
¶
resources_supported: bool
Check if MCP resources are supported by this tool source.
resources
property
¶
resources: list[ResourceInfo]
Get cached list of resources (call list_resources for refresh).
resource_templates
property
¶
resource_templates: list[ResourceTemplateInfo]
Get cached list of resource templates.
prompts_supported
property
¶
prompts_supported: bool
Check if MCP prompts are supported by this tool source.
prompts
property
¶
prompts: list[PromptInfo]
Get cached list of prompts (call list_prompts for refresh).
connect
async
¶
connect(ctx: ToolContext | None = None) -> None
Connect to tool source and discover available tools.
Idempotent: if already connected on the current event loop this is a
no-op. If the connection was established on a different event loop
(e.g. the ToolNode was built before an async server started), this
transparently reconnects on the current loop. On success, discovered
tools (and, for MCP transports, resources and prompts) are populated
and registered into self.registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctx
|
ToolContext | None
|
Optional ToolContext for HITL OAuth during connection. Required if auth_type is OAUTH2_USER. |
None
|
Raises:
| Type | Description |
|---|---|
ToolAuthError
|
If |
ToolConnectionError
|
If the transport is unsupported or the underlying MCP/UTCP client fails to connect. |
ToolNodeError
|
If a discovered tool name collides with an
already-registered name in |
get_tools
¶
get_tools() -> list[NodeSpec]
Return discovered tools as Penguiflow NodeSpec entries.
Note: connect() must be called before this method.
Returns:
| Type | Description |
|---|---|
list[NodeSpec]
|
The list of :class: |
list[NodeSpec]
|
during |
list[NodeSpec]
|
tools). Empty if |
get_tool_specs
¶
get_tool_specs() -> list[NodeSpec]
Alias for get_tools for compatibility with generators.
Returns:
| Type | Description |
|---|---|
list[NodeSpec]
|
Same value as :meth: |
call
async
¶
call(tool_name: str, args: dict[str, Any], ctx: ToolContext) -> Any
Execute a tool with auth resolution and resilience.
Reconnects automatically if the client is not connected or was
connected on a different event loop, resolves auth headers (pausing
for HITL OAuth via ctx if needed), invokes the underlying
MCP/UTCP tool with retries per config.retry_policy, and runs the
result through the layered artifact-extraction pipeline (and, for
MCP Apps-enabled tools, fetches and attaches the app UI resource).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_name
|
str
|
Namespaced tool name (e.g. |
required |
args
|
dict[str, Any]
|
Tool call arguments as a plain dict (already validated against the tool's args model by the caller/planner). |
required |
ctx
|
ToolContext
|
ToolContext for the current run, used to resolve auth, pause for HITL flows, and access the artifact store. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
A dict of the form |
Any
|
|
Any
|
replaced by artifact references. |
Raises:
| Type | Description |
|---|---|
ToolAuthError
|
If OAuth is required but cannot be resolved. |
ToolNodeError
|
If no client is available or the call fails after exhausting retries. |
close
async
¶
close() -> None
Clean up resources.
Marks the node disconnected, clears discovered tool state, and best -effort closes the underlying MCP/UTCP client (exceptions during client teardown, e.g. from a stale event loop, are swallowed). Safe to call multiple times.
list_resources
async
¶
list_resources(refresh: bool = False) -> list[ResourceInfo]
List available MCP resources.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refresh
|
bool
|
Force refresh from server |
False
|
Returns:
| Type | Description |
|---|---|
list[ResourceInfo]
|
List of ResourceInfo objects (empty if not connected or resources not supported) |
list_resource_templates
async
¶
list_resource_templates(refresh: bool = False) -> list[ResourceTemplateInfo]
List available MCP resource templates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refresh
|
bool
|
Force refresh from server |
False
|
Returns:
| Type | Description |
|---|---|
list[ResourceTemplateInfo]
|
List of ResourceTemplateInfo objects (empty if not connected or not supported) |
read_resource
async
¶
read_resource(uri: str, ctx: ToolContext, *, use_cache: bool = True) -> dict[str, Any]
Read a resource by URI.
Uses ResourceCache to avoid repeated fetches. Binary content is stored in ArtifactStore; small text may be inlined.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
Resource URI to read |
required |
ctx
|
ToolContext
|
Tool context with artifact store access |
required |
use_cache
|
bool
|
Whether to use caching (default True) |
True
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with 'artifact' (ArtifactRef) or 'text' (inline content) or 'error' |
subscribe_resource
async
¶
subscribe_resource(uri: str, callback: Any | None = None) -> bool
Subscribe to resource updates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
Resource URI to subscribe to |
required |
callback
|
Any | None
|
Optional callback for updates |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if subscription successful |
unsubscribe_resource
async
¶
unsubscribe_resource(uri: str) -> bool
Unsubscribe from resource updates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
Resource URI to unsubscribe from |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if unsubscription successful |
handle_resource_updated
¶
handle_resource_updated(uri: str) -> None
Handle a resource updated notification from MCP server.
Invalidates cache and notifies subscribers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
URI of updated resource |
required |
set_resource_updated_callback
¶
set_resource_updated_callback(callback: Callable[[str], None] | None) -> None
Register a callback invoked when MCP resources are updated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
Callable[[str], None] | None
|
Callable invoked with the updated resource URI, or
|
required |
has_app
¶
has_app(tool_name: str) -> bool
Check if a tool has MCP App metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_name
|
str
|
Namespaced tool name |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the tool has app metadata |
get_app_metadata
¶
get_app_metadata(tool_name: str) -> AppMetadata | None
Get MCP App metadata for a tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_name
|
str
|
Namespaced tool name |
required |
Returns:
| Type | Description |
|---|---|
AppMetadata | None
|
AppMetadata or None |
list_prompts
async
¶
list_prompts(refresh: bool = False) -> list[PromptInfo]
List available MCP prompts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refresh
|
bool
|
Force refresh from server |
False
|
Returns:
| Type | Description |
|---|---|
list[PromptInfo]
|
List of PromptInfo objects (empty if not connected or prompts not supported) |
get_prompt
async
¶
get_prompt(name: str, arguments: dict[str, str] | None = None) -> dict[str, Any]
Execute a prompt and return rendered messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Prompt name |
required |
arguments
|
dict[str, str] | None
|
Optional arguments for the prompt |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with 'messages' (list of serialized messages) and 'description' |
handle_prompts_changed
¶
handle_prompts_changed() -> None
Handle a prompts/list_changed notification from MCP server.
Clears cached prompts so they are re-discovered on next access.
ExternalToolConfig
¶
Bases: BaseModel
Configuration for a single external tool source consumed by ToolNode.
One instance describes how to reach and authenticate against one MCP
server or UTCP/HTTP/CLI endpoint, plus the resilience and
artifact-extraction policies applied to its tool calls. Validated at
construction time by :meth:validate_config.
name
class-attribute
instance-attribute
¶
name: str = Field(..., description="Unique namespace for tools (e.g., 'github')")
description
class-attribute
instance-attribute
¶
description: str = Field(default='', description='Human-readable description of this tool source.')
connection
class-attribute
instance-attribute
¶
connection: str = Field(..., description='Connection string (command for MCP, URL for HTTP/UTCP)')
utcp_mode
class-attribute
instance-attribute
¶
utcp_mode: UtcpMode = Field(default=UtcpMode.AUTO, description='For HTTP/UTCP: how to interpret connection (manual_url recommended)')
mcp_transport_mode
class-attribute
instance-attribute
¶
mcp_transport_mode: McpTransportMode = Field(default=McpTransportMode.AUTO, description='For MCP over HTTP: auto-detect, sse, or streamable_http')
env
class-attribute
instance-attribute
¶
env: dict[str, str] = Field(default_factory=dict, description='Environment variables passed to an MCP stdio subprocess, or used as UTCP variable substitutions. Values may reference ${VAR} to substitute from the host process environment.')
auth_type
class-attribute
instance-attribute
¶
auth_type: AuthType = Field(default=AuthType.NONE, description='Authentication method used for this tool source.')
auth_config
class-attribute
instance-attribute
¶
auth_config: dict[str, Any] = Field(default_factory=dict, description="Auth-type-specific settings, e.g. {'token': ...} for BEARER, {'api_key': ..., 'header': ...} for API_KEY, or {'cookie_name': ..., 'cookie_value': ...} for COOKIE. Values may reference ${VAR} for env substitution.")
timeout_s
class-attribute
instance-attribute
¶
timeout_s: float = Field(default=30.0, ge=1.0, le=300.0, description='Per-call timeout in seconds.')
retry_policy
class-attribute
instance-attribute
¶
retry_policy: RetryPolicy = Field(default_factory=RetryPolicy, description='Retry/backoff policy applied to tool calls.')
max_concurrency
class-attribute
instance-attribute
¶
max_concurrency: int = Field(default=10, ge=1, le=100, description='Maximum number of concurrent in-flight tool calls for this source.')
tool_filter
class-attribute
instance-attribute
¶
tool_filter: list[str] | None = Field(default=None, description='Regex patterns to include specific tools (None = all)')
arg_validation
class-attribute
instance-attribute
¶
arg_validation: dict[str, Any] = Field(default_factory=lambda: {'emit_suspect': True}, description='Planner arg validation policy for discovered tools.')
artifact_extraction
class-attribute
instance-attribute
¶
artifact_extraction: ArtifactExtractionConfig = Field(default_factory=ArtifactExtractionConfig, description='Configuration for extracting binary/large content as artifacts')
prompts
class-attribute
instance-attribute
¶
prompts: PromptsConfig = Field(default_factory=lambda: PromptsConfig(), description='Configuration for MCP prompts discovery and tool generation')
apps
class-attribute
instance-attribute
¶
apps: AppsConfig = Field(default_factory=lambda: AppsConfig(), description='Configuration for MCP Apps interactive UI support')
output_transformer
class-attribute
instance-attribute
¶
output_transformer: OutputTransformer | None = Field(default=None, exclude=True, description='Custom async function to transform tool outputs')
validate_config
¶
validate_config() -> ExternalToolConfig
Validate transport/auth-specific requirements after field parsing.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Returns:
| Type | Description |
|---|---|
ExternalToolConfig
|
The validated |
ArtifactExtractionConfig
¶
Bases: BaseModel
Configuration for extracting artifacts from tool outputs.
max_inline_size
class-attribute
instance-attribute
¶
max_inline_size: int = Field(default=10000, ge=0, description='Maximum chars before auto-artifact extraction')
auto_artifact_large_content
class-attribute
instance-attribute
¶
auto_artifact_large_content: bool = Field(default=True, description='Automatically store large content as artifacts')
binary_detection
class-attribute
instance-attribute
¶
binary_detection: BinaryDetectionConfig = Field(default_factory=BinaryDetectionConfig)
resources
class-attribute
instance-attribute
¶
resources: ResourceHandlingConfig = Field(default_factory=ResourceHandlingConfig)
handle_mcp_typed_content
class-attribute
instance-attribute
¶
handle_mcp_typed_content: bool = Field(default=True, description='Extract EmbeddedResource/blob typed content blocks')
tool_fields
class-attribute
instance-attribute
¶
tool_fields: dict[str, list[ArtifactFieldConfig]] = Field(default_factory=dict, description='Map of tool_name -> list of field extraction configs')
default_binary_summary
class-attribute
instance-attribute
¶
default_binary_summary: str = Field(default='Binary content stored as artifact ({mime_type}, {size} bytes). Artifact ID: {artifact_id}', description='Format string for the LLM-facing summary of extracted binary content. Supports {mime_type}, {size}, and {artifact_id} placeholders.')
default_text_summary
class-attribute
instance-attribute
¶
default_text_summary: str = Field(default='Large text stored as artifact ({size} chars). Artifact ID: {artifact_id}', description='Format string for the LLM-facing summary of extracted oversized text. Supports {size} and {artifact_id} placeholders.')
ArtifactFieldConfig
¶
Bases: BaseModel
Configuration for extracting specific fields as artifacts.
field_path
class-attribute
instance-attribute
¶
field_path: str = Field(..., description="JSONPath or dot notation to the field (e.g., 'content' or 'result.pdf_data')")
content_type
class-attribute
instance-attribute
¶
content_type: str = Field(..., description='Expected content type: pdf, image, binary, text')
mime_type
class-attribute
instance-attribute
¶
mime_type: str | None = Field(default=None, description='Override MIME type (auto-detected if None)')
summary_template
class-attribute
instance-attribute
¶
summary_template: str = Field(default='Downloaded {content_type} ({size} bytes)', description='Template for LLM summary')
AuthType
¶
Bases: str, Enum
Authentication methods supported by ToolNode.
Values
NONE: No authentication.
API_KEY: Static API key injected as a request header.
BEARER: Static bearer token injected as an Authorization header.
COOKIE: Cookie-based auth (e.g. a Databricks Apps session cookie).
OAUTH2_USER: Per-user OAuth 2.0 authorization-code flow, resolved via
a human-in-the-loop (HITL) pause using OAuthManager.
BinaryDetectionConfig
¶
Bases: BaseModel
Configuration for automatic binary content detection.
enabled
class-attribute
instance-attribute
¶
enabled: bool = Field(default=True, description='Enable binary content detection')
signatures
class-attribute
instance-attribute
¶
signatures: dict[str, tuple[str, str]] = Field(default_factory=lambda: dict(DEFAULT_BINARY_SIGNATURES), description='Map of base64 prefix -> (extension, mime_type)')
min_size_for_detection
class-attribute
instance-attribute
¶
min_size_for_detection: int = Field(default=1000, ge=0, description='Minimum string length to check for binary content')
max_decode_bytes
class-attribute
instance-attribute
¶
max_decode_bytes: int = Field(default=5000000, ge=1000, description='Maximum bytes to decode when probing binary content')
require_magic_bytes
class-attribute
instance-attribute
¶
require_magic_bytes: bool = Field(default=True, description='Require magic byte validation after base64 decode')
ResourceHandlingConfig
¶
Bases: BaseModel
Policy for MCP resources and resource_links.
enabled
class-attribute
instance-attribute
¶
enabled: bool = Field(default=True, description='Enable resource link handling')
auto_read_if_size_under_bytes
class-attribute
instance-attribute
¶
auto_read_if_size_under_bytes: int = Field(default=0, ge=0, description='Auto-read resources smaller than this (0 = never auto-read)')
inline_text_if_under_chars
class-attribute
instance-attribute
¶
inline_text_if_under_chars: int = Field(default=10000, ge=0, description='Inline text resources smaller than this')
cache_reads_to_artifacts
class-attribute
instance-attribute
¶
cache_reads_to_artifacts: bool = Field(default=True, description='Cache resource reads to artifact store')
RetryPolicy
¶
Bases: BaseModel
Retry configuration for ToolNode tool calls, using tenacity semantics.
Consumed by ToolNode._call_with_retry, which retries only on
retryable errors (timeouts, connection errors, or ToolNodeError
subclasses marked retryable) with exponential backoff between attempts.
max_attempts
class-attribute
instance-attribute
¶
max_attempts: int = Field(default=3, ge=1, le=10, description='Maximum number of call attempts, including the first (non-retry) attempt.')
wait_exponential_min_s
class-attribute
instance-attribute
¶
wait_exponential_min_s: float = Field(default=0.1, ge=0.01, description='Minimum wait time in seconds before a retry (exponential backoff floor).')
wait_exponential_max_s
class-attribute
instance-attribute
¶
wait_exponential_max_s: float = Field(default=5.0, ge=0.1, description='Maximum wait time in seconds before a retry (exponential backoff ceiling).')
retry_on_status
class-attribute
instance-attribute
¶
retry_on_status: list[int] = Field(default_factory=lambda: [429, 500, 502, 503, 504], description='HTTP status codes that are treated as retryable when surfaced by adapters.')
TransportType
¶
Bases: str, Enum
Supported communication protocols for an external tool source.
Values
MCP: Model Context Protocol via FastMCP; transport (stdio/SSE/HTTP)
is auto-detected from ExternalToolConfig.connection.
HTTP: REST API accessed via UTCP.
UTCP: Native UTCP endpoint (manual or call-template based).
CLI: Command-line tools invoked via UTCP's CLI call template.
UtcpMode
¶
Bases: str, Enum
How to interpret the UTCP connection string on ExternalToolConfig.
Values
AUTO: Try MANUAL_URL first (if the connection string looks like a
manual document), falling back to BASE_URL otherwise.
MANUAL_URL: connection points directly to a UTCP manual document
(recommended: enables full tool discovery).
BASE_URL: connection is a plain REST base URL with a synthesized
call template (limited discovery, no manual document).
OAuthManager
dataclass
¶
OAuthManager(providers: dict[str, OAuthProviderConfig], token_store: TokenStore = InMemoryTokenStore(), _pending: dict[str, dict[str, float | str]] = dict())
Manages user OAuth flows with HITL integration.
Used by ToolNode when a tool source's auth_type is
AuthType.OAUTH2_USER: the manager checks the token store for a cached
token, and if none is available builds an authorization URL for the
caller to present to the user (typically via a HITL pause), then
completes the flow via :meth:handle_callback once the provider
redirects back with an authorization code.
Attributes:
| Name | Type | Description |
|---|---|---|
providers |
dict[str, OAuthProviderConfig]
|
Mapping of provider name to its
:class: |
token_store |
TokenStore
|
Backing :class: |
token_store
class-attribute
instance-attribute
¶
token_store: TokenStore = field(default_factory=InMemoryTokenStore)
get_token
async
¶
get_token(user_id: str, provider: str) -> str | None
Look up a cached access token for a user/provider pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the user to look up. |
required |
provider
|
str
|
OAuth provider name. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The cached token, or |
get_auth_request
¶
get_auth_request(provider: str, user_id: str, trace_id: str) -> dict[str, str | list[str]]
Build an authorization request for a HITL OAuth consent flow.
Generates and tracks a random state value (pruning any expired
pending requests first) and constructs the provider's authorization
URL. The returned payload is typically surfaced to the user via
ctx.pause() so they can complete consent out-of-band.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
str
|
OAuth provider name (must exist in |
required |
user_id
|
str
|
Identifier of the user initiating the flow. |
required |
trace_id
|
str
|
Trace ID of the run requesting authorization, used to
resume the correct run in :meth: |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str | list[str]]
|
Dict with |
dict[str, str | list[str]]
|
including the generated |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
handle_callback
async
¶
handle_callback(code: str, state: str) -> tuple[str, str]
Complete an OAuth authorization-code exchange and cache the token.
Consumes the pending request created by :meth:get_auth_request,
exchanges code for an access token at the provider's token
endpoint, and stores the token in self.token_store for future
:meth:get_token lookups.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
Authorization code returned by the provider's redirect. |
required |
state
|
str
|
The |
required |
Returns:
| Type | Description |
|---|---|
str
|
Tuple of |
str
|
initiated the flow, so the caller can resume the paused run. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
RuntimeError
|
If |
OAuthProviderConfig
dataclass
¶
OAuthProviderConfig(name: str, display_name: str, auth_url: str, token_url: str, client_id: str, client_secret: str, redirect_uri: str, scopes: list[str] = list())
Static configuration describing a single OAuth 2.0 authorization-code provider.
One instance is registered per provider in OAuthManager.providers, keyed
by name.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Unique provider key (matches the key used in
|
display_name |
str
|
Human-readable provider name shown to end users during the HITL consent flow. |
auth_url |
str
|
Provider's authorization endpoint (where the user is redirected to grant consent). |
token_url |
str
|
Provider's token endpoint used to exchange an authorization code for an access token. |
client_id |
str
|
OAuth client ID registered with the provider. |
client_secret |
str
|
OAuth client secret registered with the provider. |
redirect_uri |
str
|
Redirect URI registered with the provider that the authorization server will send the user back to. |
scopes |
list[str]
|
OAuth scopes to request during authorization. Defaults to an empty list (no scopes requested). |
TokenStore
¶
Bases: Protocol
Persistence contract for OAuth access tokens.
Implement this protocol to back :class:OAuthManager with a durable
store (e.g. a database or secrets manager) instead of the default
:class:InMemoryTokenStore. Tokens are keyed by (user_id, provider).
store
async
¶
store(user_id: str, provider: str, token: str, expires_at: float | None) -> None
Persist an access token for a user/provider pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the user the token belongs to. |
required |
provider
|
str
|
OAuth provider name (matches
:attr: |
required |
token
|
str
|
The access token value to store. |
required |
expires_at
|
float | None
|
Unix timestamp (seconds) after which the token is
considered expired, or |
required |
get
async
¶
get(user_id: str, provider: str) -> str | None
Retrieve a previously stored access token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the user the token belongs to. |
required |
provider
|
str
|
OAuth provider name. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The stored token, or |
delete
async
¶
delete(user_id: str, provider: str) -> None
Remove a stored access token, if present.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the user the token belongs to. |
required |
provider
|
str
|
OAuth provider name. |
required |
InMemoryTokenStore
¶
InMemoryTokenStore()
Simple in-memory :class:TokenStore for development and tests.
Tokens are held only in process memory (a plain dict keyed by
(user_id, provider)) and are lost on process restart. Not suitable
for production multi-worker deployments.
store
async
¶
store(user_id: str, provider: str, token: str, expires_at: float | None) -> None
Store a token in memory, overwriting any existing entry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the user the token belongs to. |
required |
provider
|
str
|
OAuth provider name. |
required |
token
|
str
|
The access token value to store. |
required |
expires_at
|
float | None
|
Unix timestamp (seconds) after which the token is
considered expired, or |
required |
get
async
¶
get(user_id: str, provider: str) -> str | None
Fetch a token, transparently evicting it if expired.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the user the token belongs to. |
required |
provider
|
str
|
OAuth provider name. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The stored token, or |
delete
async
¶
delete(user_id: str, provider: str) -> None
Remove a stored token, if present.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the user the token belongs to. |
required |
provider
|
str
|
OAuth provider name. |
required |
ErrorCategory
¶
Bases: str, Enum
Classification for retry decisions.
ToolNodeError
¶
ToolNodeError(message: str, *, status_code: int | None = None, category: ErrorCategory | None = None, cause: Exception | None = None)
ToolAuthError
¶
ToolAuthError(message: str, *, status_code: int | None = None, category: ErrorCategory | None = None, cause: Exception | None = None)
ToolClientError
¶
ToolClientError(message: str, *, status_code: int | None = None, category: ErrorCategory | None = None, cause: Exception | None = None)
ToolConnectionError
¶
ToolConnectionError(message: str, *, status_code: int | None = None, category: ErrorCategory | None = None, cause: Exception | None = None)
ToolRateLimitError
¶
ToolRateLimitError(message: str, *, status_code: int | None = None, category: ErrorCategory | None = None, cause: Exception | None = None)
ToolServerError
¶
ToolServerError(message: str, *, status_code: int | None = None, category: ErrorCategory | None = None, cause: Exception | None = None)
ToolTimeoutError
¶
ToolTimeoutError(message: str, *, status_code: int | None = None, category: ErrorCategory | None = None, cause: Exception | None = None)
get_preset
¶
get_preset(name: str) -> ExternalToolConfig
Get a pre-configured MCP server config by name.
Looks up name in :data:POPULAR_MCP_SERVERS. These presets default
to npx -y ... stdio connections intended for local development; see
the module docstring for production alternatives.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Preset key (e.g. |
required |
Returns:
| Type | Description |
|---|---|
ExternalToolConfig
|
The preset's :class: |
ExternalToolConfig
|
Note this returns the shared preset instance, not a copy. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
get_artifact_preset
¶
get_artifact_preset(name: str) -> ArtifactExtractionConfig
Get an artifact extraction preset by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Preset name (e.g., 'tableau', 'github', 'filesystem'). |
required |
Returns:
| Type | Description |
|---|---|
ArtifactExtractionConfig
|
ArtifactExtractionConfig for the named preset. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the preset name is not found. |
Example
from penguiflow.tools.presets import get_artifact_preset
config = ExternalToolConfig( name="tableau", transport=TransportType.MCP, connection="http://tableau-mcp:8080/sse", artifact_extraction=get_artifact_preset("tableau"), )
get_artifact_preset_info
¶
get_artifact_preset_info(name: str) -> dict[str, Any]
Get information about an artifact preset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Preset name. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with preset metadata. |
get_artifact_preset_with_overrides
¶
get_artifact_preset_with_overrides(name: str, **overrides: Any) -> ArtifactExtractionConfig
Get an artifact preset with field overrides.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Preset name. |
required |
**overrides
|
Any
|
Field overrides to apply. |
{}
|
Returns:
| Type | Description |
|---|---|
ArtifactExtractionConfig
|
ArtifactExtractionConfig with overrides applied. |
Example
config = get_artifact_preset_with_overrides( "tableau", max_inline_size=2000, )
list_artifact_presets
¶
list_artifact_presets() -> list[str]
List available artifact preset names.
Returns:
| Type | Description |
|---|---|
list[str]
|
Sorted list of preset keys registered in :data: |
list[str]
|
(e.g. |
merge_artifact_preset
¶
merge_artifact_preset(base: ArtifactExtractionConfig, preset_name: str) -> ArtifactExtractionConfig
Merge a preset's tool_fields into an existing config.
Useful for combining preset tool field mappings with custom settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base
|
ArtifactExtractionConfig
|
The base configuration to merge into. |
required |
preset_name
|
str
|
Name of the preset to merge from. |
required |
Returns:
| Type | Description |
|---|---|
ArtifactExtractionConfig
|
New ArtifactExtractionConfig with merged tool_fields. |
Example
custom = ArtifactExtractionConfig(max_inline_size=2000) merged = merge_artifact_preset(custom, "tableau")
adapt_exception
¶
adapt_exception(exc: Exception, transport: str) -> ToolNodeError
Route to appropriate adapter based on transport.
adapt_mcp_error
¶
adapt_mcp_error(exc: Exception) -> ToolNodeError
Convert FastMCP exceptions to ToolNodeError.
adapt_utcp_error
¶
adapt_utcp_error(exc: Exception) -> ToolNodeError
Convert UTCP exceptions to ToolNodeError.