Skip to content

Repository files navigation

StackOne AI SDK

PyPI version GitHub release (latest by date) Coverage DeepWiki

StackOne AI provides a unified interface for accessing various SaaS tools through AI-friendly APIs.

Features

  • Search and execute: find an action in natural language and run it, so a catalog of hundreds of tools never has to fit in a model's context
  • Account discovery: an API key is enough — linked accounts are found for you
  • MCP-backed: tools are fetched at runtime, and the schema a model is shown is the schema the server served
  • Filtering by account, provider and glob action pattern
  • Integrations: OpenAI functions, LangChain, LangGraph, Pydantic AI

Requirements

  • Python 3.11+

Installation

uv add stackone-ai

Framework adapters are extras, imported lazily: uv add 'stackone-ai[langchain]' (LangGraph uses this one too) or uv add 'stackone-ai[pydantic-ai]'.

Quick Start

Set STACKONE_API_KEY and go. The SDK finds your linked accounts itself — you only pass an account id when you want to narrow things down.

from stackone_ai import StackOneToolSet

toolset = StackOneToolSet()

# 1. Find an action. Each hit carries the JSON Schema for its own arguments.
hits = toolset.search("list recent comments", top_k=3)
# [{"action_id": "linear_list_comments",
#   "description": "Returns a page of Linear comments as a connection object ...",
#   "similarity_score": 0.86,
#   "example_request": {"action_id": "linear_list_comments"},
#   "input_schema": {"type": "object", "properties": {"body": {...}}}}, ...]

# 2. Run it. Build the arguments from input_schema.
result = toolset.execute("linear_list_comments", {"body": {"variables": {"first": 25}}})
result["data"]  # the provider's payload — a failed call raises instead

search() asks every linked connector and returns actions ranked by similarity_score, so a catalog of hundreds of tools never has to fit in a model's context. This is the recommended way to use the SDK.

Integration Examples

Every block below runs as written against any linked account — the *_list_* filter discovers whatever your key can reach rather than assuming a provider. Each has a matching runnable script in examples/.

OpenAI

Needs no stackone-ai extra — just uv add openai.

from openai import OpenAI
from stackone_ai import StackOneToolSet

toolset = StackOneToolSet()
tools = toolset.fetch_tools(actions=["*_list_*"])
openai_tools = tools.to_openai()[:20]   # keep the catalog inside the context window

client = OpenAI()
messages = [{"role": "user", "content": "Use a tool to list a few records, then summarise them."}]
response = client.chat.completions.create(
    model="gpt-5.4", messages=messages, tools=openai_tools, tool_choice="auto"
)

message = response.choices[0].message
messages.append(message.model_dump(exclude_none=True))   # the assistant turn comes first
messages.extend(tools.execute_openai_tool_calls(message.tool_calls))

to_openai() emits Chat Completions function tools, and execute_openai_tool_calls() runs the calls the model makes and returns the tool messages to send back. A failed call becomes an error message the model can read and retry from, rather than an exception. Chat Completions accepts at most 128 tools, so filter before binding. See examples/openai_integration.py for the full round trip, including feeding results back for a final answer.

LangChain
uv add 'stackone-ai[langchain]' langchain-openai
from langchain_openai import ChatOpenAI
from stackone_ai import StackOneToolSet

toolset = StackOneToolSet()
tools = toolset.fetch_tools(actions=["*_list_*"])

model = ChatOpenAI(model="gpt-5.4").bind_tools(tools.to_langchain())
response = model.invoke("Use a tool to list a few records.")

for call in response.tool_calls:
    tool = tools.get_tool(call["name"])
    print(tool.execute(call["args"]))
Pydantic AI
uv add 'stackone-ai[pydantic-ai]'
from pydantic_ai import Agent
from stackone_ai import StackOneToolSet

toolset = StackOneToolSet()
tools = toolset.fetch_tools(actions=["*_list_*"]).to_pydantic_ai()

agent = Agent("openai:gpt-5.4", tools=tools)
print(agent.run_sync("Use a tool to list a few records, then summarise them.").output)

toolset.pydantic_ai() is the same thing for the unfiltered catalog, parallel to .openai() and .langchain().

LangGraph

LangGraph consumes LangChain tools, so it goes through the same adapter.

uv add 'stackone-ai[langchain]' langchain langchain-openai
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from stackone_ai import StackOneToolSet

toolset = StackOneToolSet()
tools = toolset.fetch_tools(actions=["*_list_*"]).to_langchain()

agent = create_agent(ChatOpenAI(model="gpt-5.4"), tools)
result = agent.invoke({"messages": [("user", "Use a tool to list a few records.")]})
print(result["messages"][-1].content)

Advanced Filtering

fetch_tools() takes three filters. They combine with AND, and all of them are applied locally to one cached listing — changing a filter never refetches.

toolset = StackOneToolSet()

toolset.fetch_tools()                                    # every tool, every active account
toolset.fetch_tools(providers=["linear"])                # one connector
toolset.fetch_tools(actions=["linear_list_*"])           # one connector's list actions
toolset.fetch_tools(actions=["linear_get_issue"])        # exactly one tool
toolset.fetch_tools(providers=["linear"],
                    actions=["*_get_*"])                 # both filters, AND-ed
toolset.fetch_tools(account_ids=["acc-123", "acc-456"])
  • account_ids — restrict to these accounts. Omit it and the SDK discovers your active accounts. An empty list means "no filter", not "no accounts". A single failing account is logged and skipped, not fatal.
  • providers — matched case-insensitively as a full prefix, so providers=["linear"] and ["LINEAR"] are the same, and a connector whose name contains an underscore must be spelled in full (["browser_linkedin"], not ["browser"]). No globs here — use actions for that.
  • actions — glob patterns, matched case-sensitively against the whole tool name. Exact (["linear_get_issue"]), prefix (["linear_*"]), infix (["*_list_*"]), and character classes (["linear_[lg]*"]) all work. Multiple patterns are OR'd together.
# Chaining: set_accounts() scopes every later call on this toolset.
toolset.set_accounts(["acc-123"])
tools = toolset.fetch_tools(providers=["linear"])

Two ways to call a tool

The SDK exposes the same actions through two surfaces. They take different argument shapes, because each mirrors the schema the server served for it. Both return the payload itself.

search() + toolset.execute() fetch_tools() + tool.execute()
Schema to read input_schema on each hit tool.parameters.properties
Argument shape nested — {"body": {"variables": {...}}} flat, prefixed — body_variables, path_id
Best for agents that discover actions at run time binding a fixed, filtered set of tools to a model
# Same action, both surfaces:
toolset.execute("linear_list_comments", {"body": {"variables": {"first": 25}}})

tool = toolset.fetch_tools(actions=["linear_list_comments"]).get_tool("linear_list_comments")
tool.execute({"body_variables": {"first": 25}})

Mixing them fails silently in one direction. A fetch_tools() tool also accepts the nested form. But flat keys like body_variables passed to toolset.execute() are not an error — they are dropped, and you get the server's defaults.

Examples

For more examples, check out the examples/ directory:

Development

Setup

Development uses uv for Python and make as the task runner.

# Core only
make install

# Everything: adapters, examples and dev tooling
make install extras=1

Common Commands

make          # list all targets
make format   # fix lint, format, and type check
make test     # run all tests

Linting, type checking and tests run in CI on every push; there are no git hooks.

Integration Tests

Tests that exercise the MCP mock server need its Node dependencies (pnpm provides tsx, which runs the server):

pnpm install

Without these, those tests fail rather than skip.

License

Apache 2.0 License

About

integrations for ai agents

Resources

Stars

4 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages