Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
with:
python-version: "3.11"
python-version: "3.9"
enable-cache: true

- name: Install all dependencies
run: uv sync --all-extras
run: uv sync --without mcp

- name: Build documentation
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
with:
python-version: "3.11"
python-version: "3.9"
enable-cache: true

- name: Install dependencies
run: uv sync --all-extras
run: uv sync --without mcp

- name: Run Ruff
uses: astral-sh/ruff-action@0c50076f12c38c3d0115b7b519b54a91cb9cf0ad # v3.5.0
Expand Down
18 changes: 16 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ on:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
include:
- python-version: "3.9"
test-extras: "--without mcp"
- python-version: "3.10"
test-extras: "--all-extras"
- python-version: "3.11"
test-extras: "--all-extras"
- python-version: "3.12"
test-extras: "--all-extras"
- python-version: "3.13"
test-extras: "--all-extras"
env:
STACKONE_API_KEY: ${{ secrets.STACKONE_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Expand All @@ -17,11 +31,11 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
with:
python-version: "3.11"
python-version: ${{ matrix.python-version }}
enable-cache: true

- name: Install dependencies
run: uv sync --all-extras
run: uv sync ${{ matrix.test-extras }}

- name: Run tests
run: uv run pytest
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,32 @@ StackOne AI provides a unified interface for accessing various SaaS tools throug
- CrewAI Tools
- LangGraph Tool Node

## Requirements

- Python 3.9+ (core SDK functionality)
- Python 3.10+ (for MCP server and CrewAI examples)

## Installation

### Basic Installation

```bash
pip install stackone-ai
```

### Optional Features

```bash
# Install with MCP server support (requires Python 3.10+)
pip install stackone-ai[mcp]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote extras in pip install to avoid shell globbing (e.g., zsh) causing 'no matches found'.

Prompt for AI agents
Address the following comment on README.md at line 38:

<comment>Quote extras in pip install to avoid shell globbing (e.g., zsh) causing &#39;no matches found&#39;.</comment>

<file context>
@@ -18,12 +18,32 @@ StackOne AI provides a unified interface for accessing various SaaS tools throug
   - CrewAI Tools
   - LangGraph Tool Node
 
+## Requirements
+
+- Python 3.9+ (core SDK functionality)
+- Python 3.10+ (for MCP server and CrewAI examples)
+
 ## Installation
</file context>
Suggested change
pip install stackone-ai[mcp]
pip install "stackone-ai[mcp]"


# Install with CrewAI examples (requires Python 3.10+)
pip install stackone-ai[examples]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote extras in pip install to prevent shell globbing errors in some shells.

Prompt for AI agents
Address the following comment on README.md at line 41:

<comment>Quote extras in pip install to prevent shell globbing errors in some shells.</comment>

<file context>
@@ -18,12 +18,32 @@ StackOne AI provides a unified interface for accessing various SaaS tools throug
   - CrewAI Tools
   - LangGraph Tool Node
 
+## Requirements
+
+- Python 3.9+ (core SDK functionality)
+- Python 3.10+ (for MCP server and CrewAI examples)
+
 ## Installation
</file context>
Suggested change
pip install stackone-ai[examples]
pip install "stackone-ai[examples]"


# Install everything
pip install stackone-ai[mcp,examples]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote combined extras in pip install to avoid shell globbing issues.

Prompt for AI agents
Address the following comment on README.md at line 44:

<comment>Quote combined extras in pip install to avoid shell globbing issues.</comment>

<file context>
@@ -18,12 +18,32 @@ StackOne AI provides a unified interface for accessing various SaaS tools throug
   - CrewAI Tools
   - LangGraph Tool Node
 
+## Requirements
+
+- Python 3.9+ (core SDK functionality)
+- Python 3.10+ (for MCP server and CrewAI examples)
+
 ## Installation
</file context>
Suggested change
pip install stackone-ai[mcp,examples]
pip install "stackone-ai[mcp,examples]"

```

## Quick Start

```python
Expand Down Expand Up @@ -82,10 +102,12 @@ for tool_call in response.tool_calls:
</details>

<details>
<summary>CrewAI Integration</summary>
<summary>CrewAI Integration (Python 3.10+)</summary>

CrewAI uses LangChain tools natively, making integration seamless:

> **Note**: CrewAI requires Python 3.10+. Install with `pip install stackone-ai[examples]`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote extras in the inline pip install example to avoid shell globbing issues in zsh and similar shells.

Prompt for AI agents
Address the following comment on README.md at line 109:

<comment>Quote extras in the inline pip install example to avoid shell globbing issues in zsh and similar shells.</comment>

<file context>
@@ -82,10 +102,12 @@ for tool_call in response.tool_calls:
 &lt;/details&gt;
 
 &lt;details&gt;
-&lt;summary&gt;CrewAI Integration&lt;/summary&gt;
+&lt;summary&gt;CrewAI Integration (Python 3.10+)&lt;/summary&gt;
 
 CrewAI uses LangChain tools natively, making integration seamless:
 
+&gt; **Note**: CrewAI requires Python 3.10+. Install with `pip install stackone-ai[examples]`
</file context>
Suggested change
> **Note**: CrewAI requires Python 3.10+. Install with `pip install stackone-ai[examples]`
> **Note**: CrewAI requires Python 3.10+. Install with `pip install "stackone-ai[examples]"`


```python
from crewai import Agent, Crew, Task
from stackone_ai import StackOneToolSet
Expand Down
18 changes: 13 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "stackone-ai"
version = "0.3.1"
description = "agents performing actions on your SaaS"
readme = "README.md"
requires-python = ">=3.11"
requires-python = ">=3.9"
authors = [
{ name = "StackOne", email = "[email protected]" }
]
Expand All @@ -12,16 +12,20 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"pydantic>=2.10.6",
"requests>=2.32.3",
"langchain-core>=0.1.0",
"mcp[cli]>=1.3.0",
"bm25s>=0.2.2",
"numpy>=1.24.0",
"typing-extensions>=4.0.0",
]

[project.scripts]
Expand All @@ -41,8 +45,12 @@ packages = ["stackone_ai"]
"py.typed" = "py.typed"

[project.optional-dependencies]
# TODO: Remove python_version conditions when Python 3.9 support is dropped
mcp = [
"mcp[cli]>=1.3.0; python_version>='3.10'",
]
examples = [
"crewai>=0.102.0",
"crewai>=0.102.0; python_version>='3.10'",
"langchain-openai>=0.3.6",
"openai>=1.63.2",
"python-dotenv>=1.0.1",
Expand Down Expand Up @@ -82,7 +90,7 @@ markers = [

[tool.ruff]
line-length = 110
target-version = "py311"
target-version = "py39"

[tool.ruff.lint]
select = [
Expand All @@ -96,7 +104,7 @@ select = [
]

[tool.mypy]
python_version = "3.11"
python_version = "3.9"
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
Expand Down
2 changes: 1 addition & 1 deletion stackone_ai/meta_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def search(self, query: str, limit: int = 5, min_score: float = 0.0) -> list[Met

# Process results
search_results = []
for idx, score in zip(results[0], scores[0], strict=False):
for idx, score in zip(results[0], scores[0]):
if score < min_score:
continue

Expand Down
46 changes: 25 additions & 21 deletions stackone_ai/models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
# TODO: Remove when Python 3.9 support is dropped
from __future__ import annotations

import asyncio
import base64
import json
from collections.abc import Sequence
from enum import Enum
from functools import partial
from typing import Annotated, Any, TypeAlias, cast
from typing import Annotated, Any, cast

import requests
from langchain_core.tools import BaseTool
from pydantic import BaseModel, BeforeValidator, Field, PrivateAttr
from requests.exceptions import RequestException

# TODO: Remove when Python 3.9 support is dropped
from typing_extensions import TypeAlias

# Type aliases for common types
JsonDict: TypeAlias = dict[str, Any]
Headers: TypeAlias = dict[str, str]
Expand Down Expand Up @@ -140,21 +146,20 @@ def _prepare_request_params(self, kwargs: JsonDict) -> tuple[str, JsonDict, Json
for key, value in kwargs.items():
param_location = self._execute_config.parameter_locations.get(key)

match param_location:
case ParameterLocation.PATH:
if param_location == ParameterLocation.PATH:
url = url.replace(f"{{{key}}}", str(value))
elif param_location == ParameterLocation.QUERY:
query_params[key] = value
elif param_location in (ParameterLocation.BODY, ParameterLocation.FILE):
body_params[key] = value
else:
# Default behavior
if f"{{{key}}}" in url:
url = url.replace(f"{{{key}}}", str(value))
case ParameterLocation.QUERY:
elif self._execute_config.method in {"GET", "DELETE"}:
query_params[key] = value
case ParameterLocation.BODY | ParameterLocation.FILE:
else:
body_params[key] = value
case _:
# Default behavior
if f"{{{key}}}" in url:
url = url.replace(f"{{{key}}}", str(value))
elif self._execute_config.method in {"GET", "DELETE"}:
query_params[key] = value
else:
body_params[key] = value

return url, body_params, query_params

Expand Down Expand Up @@ -355,13 +360,12 @@ def to_langchain(self) -> BaseTool:
python_type: type = str # Default to str
if isinstance(details, dict):
type_str = details.get("type", "string")
match type_str:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not supported in 3.9

case "number":
python_type = float
case "integer":
python_type = int
case "boolean":
python_type = bool
if type_str == "number":
python_type = float
elif type_str == "integer":
python_type = int
elif type_str == "boolean":
python_type = bool

field = Field(description=details.get("description", ""))
else:
Expand Down Expand Up @@ -480,7 +484,7 @@ def to_langchain(self) -> Sequence[BaseTool]:
"""
return [tool.to_langchain() for tool in self.tools]

def meta_tools(self) -> "Tools":
def meta_tools(self) -> Tools:
"""Return meta tools for tool discovery and execution

Meta tools enable dynamic tool discovery and execution based on natural language queries.
Expand Down
31 changes: 23 additions & 8 deletions stackone_ai/server.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
# TODO: Remove when Python 3.9 support is dropped
from __future__ import annotations

import argparse
import asyncio
import logging
import os
import sys
from typing import Any, TypeVar

import mcp.types as types
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.shared.exceptions import McpError
from mcp.types import EmbeddedResource, ErrorData, ImageContent, TextContent, Tool
# Check Python version for MCP server functionality
if sys.version_info < (3, 10):
raise RuntimeError(
"MCP server functionality requires Python 3.10+. Current version: {}.{}.{}".format(
*sys.version_info[:3]
)
)

try: # type: ignore[unreachable]
import mcp.types as types
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.shared.exceptions import McpError
from mcp.types import EmbeddedResource, ErrorData, ImageContent, TextContent, Tool
except ImportError as e:
raise ImportError("MCP dependencies not found. Install with: pip install 'stackone-ai[mcp]'") from e

from pydantic import ValidationError

from stackone_ai import StackOneToolSet
Expand Down Expand Up @@ -41,7 +56,7 @@ def tool_needs_account_id(tool_name: str) -> bool:
return True


@app.list_tools() # type: ignore[misc]
@app.list_tools()
async def list_tools() -> list[Tool]:
"""List all available StackOne tools as MCP tools."""
if not toolset:
Expand Down Expand Up @@ -99,7 +114,7 @@ async def list_tools() -> list[Tool]:
) from e


@app.call_tool() # type: ignore[misc]
@app.call_tool()
async def call_tool(
name: str, arguments: dict[str, Any]
) -> list[TextContent | ImageContent | EmbeddedResource]:
Expand Down
5 changes: 4 additions & 1 deletion stackone_ai/specs/parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# TODO: Remove when Python 3.9 support is dropped
from __future__ import annotations

import json
from pathlib import Path
from typing import Any
Expand Down Expand Up @@ -73,7 +76,7 @@ def _resolve_schema(
visited = set()

# Handle primitive types (str, int, etc)
if not isinstance(schema, dict | list):
if not isinstance(schema, (dict, list)):
return schema

if isinstance(schema, list):
Expand Down
3 changes: 3 additions & 0 deletions stackone_ai/toolset.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# TODO: Remove when Python 3.9 support is dropped
from __future__ import annotations

import fnmatch
import os
import warnings
Expand Down
Loading
Loading