Skip to main content

SAM Tool SDK

Python SDK for building tools that run in Solace Agent Mesh (SAM).

What it does

Write a Python function (or class) and expose it as a SAM tool. Type annotations become the tool's parameter schema, the docstring supplies the descriptions the LLM sees, and you return structured results and artifacts. The SAM runtime discovers the tool and invokes it per call. The only dependency is pydantic.

Installation

pip install sam-tool-sdk

Quick Start

Function-based tool with tool_cli

The canonical pattern: author a function, wrap it with tool_cli, and expose the resulting callable as a console script via pyproject.toml.

# greet_tool.py
from sam_tool_sdk import tool_cli, ToolResult, SandboxToolContextFacade


async def greet(name: str, excited: bool = False, ctx: SandboxToolContextFacade = None) -> ToolResult:
    """Greet someone by name.

    Args:
        name: who to greet
        excited: add an exclamation mark
    """
    if ctx is not None:
        ctx.send_status(f"Greeting {name}...")
    suffix = "!" if excited else "."
    return ToolResult.ok(message=f"Hello, {name}{suffix}", data={"greeted": name})


cli = tool_cli(greet)
# pyproject.toml
[project]
name = "sam-tool-greet"
version = "0.1.0"
dependencies = ["sam-tool-sdk>=0.1,<0.2"]

[project.scripts]
greet = "greet_tool:cli"

Parameter type annotations (str, bool, int, Artifact, SandboxToolContextFacade, …) and the docstring's Args: block produce the JSON Schema the LLM sees. The SandboxToolContextFacade-annotated parameter is injected at runtime; everything else becomes a tool argument. Besides send_status, artifacts and config, the facade exposes the caller's identity: ctx.user_id, ctx.session_id, ctx.app_name, ctx.task_id, and ctx.user_profile (the gateway-forwarded profile as a dict, empty when none). The profile is unsigned, so use it to scope lookups and attribute audit records, not as the sole basis for an authorization decision.

Class-based tool (DynamicTool)

For more control over the tool's schema and behavior:

from sam_tool_sdk import DynamicTool, ToolResult

class WordCounter(DynamicTool):
    @property
    def tool_name(self):
        return "count_words"

    @property
    def tool_description(self):
        return "Counts the number of words in the given text."

    @property
    def parameters_schema(self):
        return {
            "type": "object",
            "properties": {
                "text": {"type": "string", "description": "The text to count words in"},
            },
            "required": ["text"],
        }

    async def _run_async_impl(self, args, tool_context=None, credential=None):
        count = len(args["text"].split())
        return ToolResult.ok(f"Found {count} words", data={"count": count}).model_dump()

Provider-based tools (DynamicToolProvider)

When you want to define multiple related tools from a single class:

from sam_tool_sdk import DynamicToolProvider

class MathTools(DynamicToolProvider):
    @MathTools.register_tool
    async def add(self, a: float, b: float) -> dict:
        """Add two numbers."""
        return {"text": str(a + b)}

    @MathTools.register_tool
    async def multiply(self, a: float, b: float) -> dict:
        """Multiply two numbers."""
        return {"text": str(a * b)}

    def create_tools(self, tool_config=None):
        return []  # All tools defined via @register_tool

Declaring operator config (config_schema)

Tools that need operator-supplied configuration (API keys, endpoints, default channels, …) declare it with @with_config_schema so the platform can render a config form, validate per-agent overrides, and enforce required fields at deploy time.

from sam_tool_sdk import ConfigSchemaField, with_config_schema

@with_config_schema([
    ConfigSchemaField(
        key="slack_bot_token",
        type="string",
        description="Slack Bot OAuth token (xoxb-...).",
        required=True,
        secret=True,
    ),
    ConfigSchemaField(key="default_channel", type="string"),
    ConfigSchemaField(key="api_url", type="string"),
])
async def post_message(tool_config, channel: str, text: str):
    token = tool_config["slack_bot_token"]
    ...

Values are resolved at deploy time with precedence: agent override > toolset default > schema default > 422 if a required field is unset. Fields marked secret=True are redacted on API responses and rendered with a password input in the UI.

For DynamicTool subclasses, override the config_schema property to return a list of ConfigSchemaField(...).to_dict() entries.

Working with artifacts

Tools can receive and produce artifacts using type annotations:

from sam_tool_sdk import Artifact, ToolResult, DataObject, DataDisposition

async def summarize_file(doc: Artifact) -> ToolResult:
    """Summarize the contents of an uploaded document."""
    text = doc.as_text()
    summary = text[:200] + "..."  # placeholder for real summarization

    return ToolResult.ok(
        message=f"Summarized {doc.filename}",
        data={"summary": summary},
        data_objects=[
            DataObject(
                name="summary.txt",
                content=summary,
                mime_type="text/plain",
                disposition=DataDisposition.ARTIFACT,
                description="Document summary",
            )
        ],
    )

Per-tool sandbox options

Decorate a tool_cli function (or override the matching DynamicTool properties) to declare timeouts and volume requirements honored at execution time:

from sam_tool_sdk import tool_cli, tool_timeout, with_volume_params, VolumeParam, ToolResult


@tool_timeout(seconds=120)
@with_volume_params([
    VolumeParam(name="workspace", mount_path="/workspace", mode="readwrite"),
])
async def transform(input_path: str, ctx=None) -> ToolResult:
    """Long-running transformation that needs scratch space."""
    ...

cli = tool_cli(transform)

tool_timeout overrides the per-invocation timeout; with_volume_params declares mount points provisioned before the tool runs (paths surface via ctx.volume_mounts / ctx.get_volume_mount_path("/workspace"), keyed by mount path).

Packaging

A SAM tool ships as an AWS-Lambda-Layer-style directory: a python/bin/<console-script> entry point plus all dependencies pre-extracted under python/. Build that layout with a single pip install --target invocation:

pip install --target dist/python \
    --platform manylinux2014_x86_64 \
    --python-version 3.13 \
    --only-binary=:all: \
    .

The directory then contains dist/python/bin/<your-script>, dist/python/sam_tool_sdk/, dist/python/pydantic/, and your own package. Pair it with a manifest.yaml declaring executable: python/bin/<your-script>.

License

Apache-2.0

Metadata

Release files for sam-tool-sdk 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sam-tool-sdk 0.1.3
File Size Uploaded
sam_tool_sdk-0.1.3.tar.gz 64.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sam-tool-sdk 0.1.3
File Interpreter ABI Platform
sam_tool_sdk-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 118.9 kB

Release files / sam_tool_sdk-0.1.3.tar.gz

Download URL sam_tool_sdk-0.1.3.tar.gz
Size 64.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5a63dd2c8ae857e785ceda188eff1f95b37a9299f2f4050f5da4b7773d1f22fb
BLAKE2b-256 checksum
How to use checksums
99262da6f05c2cb8d43403468d99e444fbe57c21976e4b7ac2c9a378bd3122a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / sam_tool_sdk-0.1.3-py3-none-any.whl

Download URL sam_tool_sdk-0.1.3-py3-none-any.whl
Size 54.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0b337cb4ff78402dda868e09fcc96632497958cae0bf9178e45d397910f4de3c
BLAKE2b-256 checksum
How to use checksums
37b8339d93ab25ae3b8a9a0e541c6e0e8de233a03ade781bb099dee08b310465
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page