Skip to content

Commit 1a9bda2

Browse files
committed
fix: add type ignore for unreachable code in MCP server
This is needed because mypy sees the Python version check as always false when running on Python 3.10+, making the subsequent code unreachable. The code is actually reachable at runtime when Python 3.10+ is used.
1 parent 7ae344f commit 1a9bda2

File tree

10 files changed

+1454
-289
lines changed

10 files changed

+1454
-289
lines changed

.github/workflows/docs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ jobs:
1717
- name: Install uv
1818
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
1919
with:
20-
python-version: "3.11"
20+
python-version: "3.9"
2121
enable-cache: true
2222

2323
- name: Install all dependencies
24-
run: uv sync --all-extras
24+
run: uv sync --without server
2525

2626
- name: Build documentation
2727
run: |

.github/workflows/lint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ jobs:
1414
- name: Install uv
1515
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
1616
with:
17-
python-version: "3.11"
17+
python-version: "3.9"
1818
enable-cache: true
1919

2020
- name: Install dependencies
21-
run: uv sync --all-extras
21+
run: uv sync --without server
2222

2323
- name: Run Ruff
2424
uses: astral-sh/ruff-action@0c50076f12c38c3d0115b7b519b54a91cb9cf0ad # v3.5.0

.github/workflows/test.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ on:
88
jobs:
99
test:
1010
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
14+
include:
15+
- python-version: "3.9"
16+
test-extras: "--without server"
17+
- python-version: "3.10"
18+
test-extras: "--all-extras"
19+
- python-version: "3.11"
20+
test-extras: "--all-extras"
21+
- python-version: "3.12"
22+
test-extras: "--all-extras"
23+
- python-version: "3.13"
24+
test-extras: "--all-extras"
1125
env:
1226
STACKONE_API_KEY: ${{ secrets.STACKONE_API_KEY }}
1327
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -17,11 +31,11 @@ jobs:
1731
- name: Install uv
1832
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
1933
with:
20-
python-version: "3.11"
34+
python-version: ${{ matrix.python-version }}
2135
enable-cache: true
2236

2337
- name: Install dependencies
24-
run: uv sync --all-extras
38+
run: uv sync ${{ matrix.test-extras }}
2539

2640
- name: Run tests
2741
run: uv run pytest

pyproject.toml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "stackone-ai"
33
version = "0.3.1"
44
description = "agents performing actions on your SaaS"
55
readme = "README.md"
6-
requires-python = ">=3.11"
6+
requires-python = ">=3.9"
77
authors = [
88
{ name = "StackOne", email = "[email protected]" }
99
]
@@ -12,16 +12,20 @@ classifiers = [
1212
"Intended Audience :: Developers",
1313
"License :: OSI Approved :: MIT License",
1414
"Programming Language :: Python :: 3",
15+
"Programming Language :: Python :: 3.9",
16+
"Programming Language :: Python :: 3.10",
1517
"Programming Language :: Python :: 3.11",
18+
"Programming Language :: Python :: 3.12",
19+
"Programming Language :: Python :: 3.13",
1620
"Topic :: Software Development :: Libraries :: Python Modules",
1721
]
1822
dependencies = [
1923
"pydantic>=2.10.6",
2024
"requests>=2.32.3",
2125
"langchain-core>=0.1.0",
22-
"mcp[cli]>=1.3.0",
2326
"bm25s>=0.2.2",
2427
"numpy>=1.24.0",
28+
"typing-extensions>=4.0.0",
2529
]
2630

2731
[project.scripts]
@@ -41,8 +45,12 @@ packages = ["stackone_ai"]
4145
"py.typed" = "py.typed"
4246

4347
[project.optional-dependencies]
48+
# TODO: Remove python_version conditions when Python 3.9 support is dropped
49+
server = [
50+
"mcp[cli]>=1.3.0; python_version>='3.10'",
51+
]
4452
examples = [
45-
"crewai>=0.102.0",
53+
"crewai>=0.102.0; python_version>='3.10'",
4654
"langchain-openai>=0.3.6",
4755
"openai>=1.63.2",
4856
"python-dotenv>=1.0.1",
@@ -82,7 +90,7 @@ markers = [
8290

8391
[tool.ruff]
8492
line-length = 110
85-
target-version = "py311"
93+
target-version = "py39"
8694

8795
[tool.ruff.lint]
8896
select = [
@@ -96,7 +104,7 @@ select = [
96104
]
97105

98106
[tool.mypy]
99-
python_version = "3.11"
107+
python_version = "3.9"
100108
disallow_untyped_defs = true
101109
disallow_incomplete_defs = true
102110
check_untyped_defs = true

stackone_ai/meta_tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def search(self, query: str, limit: int = 5, min_score: float = 0.0) -> list[Met
8282

8383
# Process results
8484
search_results = []
85-
for idx, score in zip(results[0], scores[0], strict=False):
85+
for idx, score in zip(results[0], scores[0]):
8686
if score < min_score:
8787
continue
8888

stackone_ai/models.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
# TODO: Remove when Python 3.9 support is dropped
2+
from __future__ import annotations
3+
14
import asyncio
25
import base64
36
import json
47
from collections.abc import Sequence
58
from enum import Enum
69
from functools import partial
7-
from typing import Annotated, Any, TypeAlias, cast
10+
from typing import Annotated, Any, cast
811

912
import requests
1013
from langchain_core.tools import BaseTool
1114
from pydantic import BaseModel, BeforeValidator, Field, PrivateAttr
1215
from requests.exceptions import RequestException
1316

17+
# TODO: Remove when Python 3.9 support is dropped
18+
from typing_extensions import TypeAlias
19+
1420
# Type aliases for common types
1521
JsonDict: TypeAlias = dict[str, Any]
1622
Headers: TypeAlias = dict[str, str]
@@ -140,21 +146,20 @@ def _prepare_request_params(self, kwargs: JsonDict) -> tuple[str, JsonDict, Json
140146
for key, value in kwargs.items():
141147
param_location = self._execute_config.parameter_locations.get(key)
142148

143-
match param_location:
144-
case ParameterLocation.PATH:
149+
if param_location == ParameterLocation.PATH:
150+
url = url.replace(f"{{{key}}}", str(value))
151+
elif param_location == ParameterLocation.QUERY:
152+
query_params[key] = value
153+
elif param_location in (ParameterLocation.BODY, ParameterLocation.FILE):
154+
body_params[key] = value
155+
else:
156+
# Default behavior
157+
if f"{{{key}}}" in url:
145158
url = url.replace(f"{{{key}}}", str(value))
146-
case ParameterLocation.QUERY:
159+
elif self._execute_config.method in {"GET", "DELETE"}:
147160
query_params[key] = value
148-
case ParameterLocation.BODY | ParameterLocation.FILE:
161+
else:
149162
body_params[key] = value
150-
case _:
151-
# Default behavior
152-
if f"{{{key}}}" in url:
153-
url = url.replace(f"{{{key}}}", str(value))
154-
elif self._execute_config.method in {"GET", "DELETE"}:
155-
query_params[key] = value
156-
else:
157-
body_params[key] = value
158163

159164
return url, body_params, query_params
160165

@@ -355,13 +360,12 @@ def to_langchain(self) -> BaseTool:
355360
python_type: type = str # Default to str
356361
if isinstance(details, dict):
357362
type_str = details.get("type", "string")
358-
match type_str:
359-
case "number":
360-
python_type = float
361-
case "integer":
362-
python_type = int
363-
case "boolean":
364-
python_type = bool
363+
if type_str == "number":
364+
python_type = float
365+
elif type_str == "integer":
366+
python_type = int
367+
elif type_str == "boolean":
368+
python_type = bool
365369

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

483-
def meta_tools(self) -> "Tools":
487+
def meta_tools(self) -> Tools:
484488
"""Return meta tools for tool discovery and execution
485489
486490
Meta tools enable dynamic tool discovery and execution based on natural language queries.

stackone_ai/server.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
1+
# TODO: Remove when Python 3.9 support is dropped
2+
from __future__ import annotations
3+
14
import argparse
25
import asyncio
36
import logging
47
import os
58
import sys
69
from typing import Any, TypeVar
710

8-
import mcp.types as types
9-
from mcp.server import NotificationOptions, Server
10-
from mcp.server.models import InitializationOptions
11-
from mcp.server.stdio import stdio_server
12-
from mcp.shared.exceptions import McpError
13-
from mcp.types import EmbeddedResource, ErrorData, ImageContent, TextContent, Tool
11+
# Check Python version for MCP server functionality
12+
if sys.version_info < (3, 10):
13+
raise RuntimeError(
14+
"MCP server functionality requires Python 3.10+. Current version: {}.{}.{}".format(
15+
*sys.version_info[:3]
16+
)
17+
)
18+
19+
try: # type: ignore[unreachable]
20+
import mcp.types as types
21+
from mcp.server import NotificationOptions, Server
22+
from mcp.server.models import InitializationOptions
23+
from mcp.server.stdio import stdio_server
24+
from mcp.shared.exceptions import McpError
25+
from mcp.types import EmbeddedResource, ErrorData, ImageContent, TextContent, Tool
26+
except ImportError as e:
27+
raise ImportError("MCP dependencies not found. Install with: pip install 'stackone-ai[server]'") from e
28+
1429
from pydantic import ValidationError
1530

1631
from stackone_ai import StackOneToolSet
@@ -41,7 +56,7 @@ def tool_needs_account_id(tool_name: str) -> bool:
4156
return True
4257

4358

44-
@app.list_tools() # type: ignore[misc]
59+
@app.list_tools()
4560
async def list_tools() -> list[Tool]:
4661
"""List all available StackOne tools as MCP tools."""
4762
if not toolset:
@@ -99,7 +114,7 @@ async def list_tools() -> list[Tool]:
99114
) from e
100115

101116

102-
@app.call_tool() # type: ignore[misc]
117+
@app.call_tool()
103118
async def call_tool(
104119
name: str, arguments: dict[str, Any]
105120
) -> list[TextContent | ImageContent | EmbeddedResource]:

stackone_ai/specs/parser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# TODO: Remove when Python 3.9 support is dropped
2+
from __future__ import annotations
3+
14
import json
25
from pathlib import Path
36
from typing import Any
@@ -73,7 +76,7 @@ def _resolve_schema(
7376
visited = set()
7477

7578
# Handle primitive types (str, int, etc)
76-
if not isinstance(schema, dict | list):
79+
if not isinstance(schema, (dict, list)):
7780
return schema
7881

7982
if isinstance(schema, list):

stackone_ai/toolset.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# TODO: Remove when Python 3.9 support is dropped
2+
from __future__ import annotations
3+
14
import fnmatch
25
import os
36
import warnings

0 commit comments

Comments
 (0)