Skip to content

Commit fe3bb43

Browse files
author
SitHub Maintainer
committed
feat: add agent integration — Python SDK, MCP server, LLM tool-use schema
- sit/sdk.py: Python SDK with Sit class wrapping all CLI commands as direct API calls - sit/mcp_server.py: MCP stdio server exposing 7 tools (info/validate/test/diff/pr_summary/report/doctor) - sit/tool_use.py: LLM tool-use JSON Schema for OpenAI and Anthropic function calling - pyproject.toml: add [mcp] optional deps group and sit-mcp-server entry point - README.md: add Agent Integration section documenting SDK, MCP, and tool-use usage
1 parent e437d08 commit fe3bb43

6 files changed

Lines changed: 763 additions & 1 deletion

File tree

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,60 @@ sit test "$SIT_PACKAGE_DIR" --run
120120
sit ci-summary "$SIT_PACKAGE_DIR" --compare origin/main..HEAD >> "$GITHUB_STEP_SUMMARY"
121121
```
122122

123+
## Agent Integration
124+
125+
`sit` can be called by AI agents in three ways:
126+
127+
### Python SDK
128+
129+
```python
130+
from sit.sdk import Sit
131+
132+
s = Sit("./my-skill-package")
133+
info = s.info() # sit.info.v1 contract
134+
test = s.test() # sit.test.v1 contract
135+
diff = s.diff("./old") # sit.diff.v1 contract
136+
pr = s.pr_summary("./old") # sit.pr_summary.v1 contract
137+
report = s.report(compare="./old") # sit.report.v1 contract
138+
```
139+
140+
### MCP Server
141+
142+
Install with MCP support and run the stdio server:
143+
144+
```bash
145+
pip install 'sit-toolkit[mcp]'
146+
sit-mcp-server
147+
```
148+
149+
Or configure in your MCP client (e.g., Claude Desktop):
150+
151+
```json
152+
{
153+
"mcpServers": {
154+
"sit": {
155+
"command": "sit-mcp-server"
156+
}
157+
}
158+
}
159+
```
160+
161+
Exposes 7 tools: `sit_info`, `sit_validate`, `sit_test`, `sit_diff`, `sit_pr_summary`, `sit_report`, `sit_doctor`.
162+
163+
### LLM Tool-Use Schema
164+
165+
```python
166+
from sit.tool_use import get_tools_openai, get_tools_anthropic
167+
168+
# For OpenAI
169+
tools = get_tools_openai()
170+
response = client.chat_completion(messages=..., tools=tools)
171+
172+
# For Anthropic Claude
173+
tools = get_tools_anthropic()
174+
response = client.messages.create(messages=..., tools=tools)
175+
```
176+
123177
## Research
124178

125179
`sit` has been validated through multi-agent experiments on AI Skill packaging workflows. See:

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ dev = [
3939
binary = [
4040
"pyinstaller>=6.0",
4141
]
42+
mcp = [
43+
"mcp>=1.0",
44+
]
4245

4346
[project.urls]
4447
Homepage = "https://github.com/OpenRaiser/SitHub"
@@ -48,6 +51,7 @@ Issues = "https://github.com/OpenRaiser/SitHub/issues"
4851

4952
[project.scripts]
5053
sit = "sit.cli:main"
54+
sit-mcp-server = "sit.mcp_server:main"
5155

5256
[tool.setuptools]
5357
packages = ["sit"]

sit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Skill Iteration Toolkit."""
22

3-
__version__ = "0.19.0"
3+
__version__ = "0.20.0"

sit/mcp_server.py

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
"""MCP (Model Context Protocol) server for sit.
2+
3+
Exposes sit commands as MCP tools so LLM agents can call them natively
4+
via the MCP protocol. Uses the Python SDK under the hood — no subprocess.
5+
6+
Usage::
7+
8+
# Run as stdio MCP server (default transport):
9+
python -m sit.mcp_server
10+
11+
# Or import and customise:
12+
from sit.mcp_server import create_server
13+
server = create_server()
14+
server.run()
15+
"""
16+
17+
from __future__ import annotations
18+
19+
import json
20+
from pathlib import Path
21+
from typing import Any
22+
23+
try:
24+
from mcp.server import Server
25+
from mcp.server.stdio import stdio_server
26+
from mcp.types import TextContent, Tool
27+
except ImportError:
28+
raise ImportError(
29+
"MCP support requires the 'mcp' package. "
30+
"Install it with: pip install 'sit-toolkit[mcp]' or pip install mcp"
31+
)
32+
33+
from .sdk import Sit
34+
35+
# ---------------------------------------------------------------------------
36+
# Tool definitions
37+
# ---------------------------------------------------------------------------
38+
39+
TOOLS: list[Tool] = [
40+
Tool(
41+
name="sit_info",
42+
description=(
43+
"Get package info for a sit skill package. "
44+
"Returns the sit.info.v1 contract: name, version, git state, "
45+
"file listing, validation status, golden test results, reports."
46+
),
47+
inputSchema={
48+
"type": "object",
49+
"properties": {
50+
"package_path": {
51+
"type": "string",
52+
"description": "Path to the skill package directory or skill.yaml file.",
53+
},
54+
},
55+
"required": ["package_path"],
56+
},
57+
),
58+
Tool(
59+
name="sit_validate",
60+
description=(
61+
"Validate a sit skill package structure. "
62+
"Checks skill.yaml fields, prompt/schema/test paths, schema validity, "
63+
"JSONL format. Returns pass/fail with messages."
64+
),
65+
inputSchema={
66+
"type": "object",
67+
"properties": {
68+
"package_path": {
69+
"type": "string",
70+
"description": "Path to the skill package directory or skill.yaml file.",
71+
},
72+
},
73+
"required": ["package_path"],
74+
},
75+
),
76+
Tool(
77+
name="sit_test",
78+
description=(
79+
"Run validation + golden schema tests on a sit skill package. "
80+
"Returns the sit.test.v1 contract with validation and test results."
81+
),
82+
inputSchema={
83+
"type": "object",
84+
"properties": {
85+
"package_path": {
86+
"type": "string",
87+
"description": "Path to the skill package directory or skill.yaml file.",
88+
},
89+
"run_actual": {
90+
"type": "boolean",
91+
"description": "If true, run the actual test runner (commands.run_case) instead of static validation only.",
92+
"default": False,
93+
},
94+
"runner": {
95+
"type": "string",
96+
"description": "Override the test runner command (default: from skill.yaml commands.run_case).",
97+
},
98+
"timeout": {
99+
"type": "integer",
100+
"description": "Runner timeout in seconds (default: 30).",
101+
"default": 30,
102+
},
103+
},
104+
"required": ["package_path"],
105+
},
106+
),
107+
Tool(
108+
name="sit_diff",
109+
description=(
110+
"Semantic diff between two sit skill packages. "
111+
"Returns the sit.diff.v1 contract with change events, risk assessment, "
112+
"and suggested version bump."
113+
),
114+
inputSchema={
115+
"type": "object",
116+
"properties": {
117+
"old_path": {
118+
"type": "string",
119+
"description": "Path to the baseline/old skill package.",
120+
},
121+
"new_path": {
122+
"type": "string",
123+
"description": "Path to the current/new skill package.",
124+
},
125+
"include_text_diffs": {
126+
"type": "boolean",
127+
"description": "Include full unified diff lines in text_diffs (default: false).",
128+
"default": False,
129+
},
130+
},
131+
"required": ["old_path", "new_path"],
132+
},
133+
),
134+
Tool(
135+
name="sit_pr_summary",
136+
description=(
137+
"Generate a PR summary for a skill package change. "
138+
"Returns the sit.pr_summary.v1 contract with baseline/current refs, "
139+
"validation, tests, risk, and semantic diff."
140+
),
141+
inputSchema={
142+
"type": "object",
143+
"properties": {
144+
"baseline_path": {
145+
"type": "string",
146+
"description": "Path to the baseline skill package.",
147+
},
148+
"current_path": {
149+
"type": "string",
150+
"description": "Path to the current skill package.",
151+
},
152+
},
153+
"required": ["baseline_path", "current_path"],
154+
},
155+
),
156+
Tool(
157+
name="sit_report",
158+
description=(
159+
"Build a full sit report for a skill package. "
160+
"Returns the sit.report.v1 contract with validation, golden tests, "
161+
"optional diff, and reproducibility commands."
162+
),
163+
inputSchema={
164+
"type": "object",
165+
"properties": {
166+
"package_path": {
167+
"type": "string",
168+
"description": "Path to the skill package directory or skill.yaml file.",
169+
},
170+
"compare_path": {
171+
"type": "string",
172+
"description": "Optional path to a baseline package for diff comparison.",
173+
},
174+
},
175+
"required": ["package_path"],
176+
},
177+
),
178+
Tool(
179+
name="sit_doctor",
180+
description=(
181+
"Run environment diagnostics for a sit skill package. "
182+
"Checks git, GitHub remote, manifest, validation, and golden tests."
183+
),
184+
inputSchema={
185+
"type": "object",
186+
"properties": {
187+
"package_path": {
188+
"type": "string",
189+
"description": "Path to the skill package directory or skill.yaml file.",
190+
},
191+
},
192+
"required": ["package_path"],
193+
},
194+
),
195+
]
196+
197+
198+
# ---------------------------------------------------------------------------
199+
# Handler
200+
# ---------------------------------------------------------------------------
201+
202+
def _handle_tool(name: str, arguments: dict[str, Any]) -> str:
203+
"""Dispatch an MCP tool call to the SDK and return JSON string."""
204+
try:
205+
if name == "sit_info":
206+
result = Sit(arguments["package_path"]).info()
207+
elif name == "sit_validate":
208+
result = Sit(arguments["package_path"]).validate()
209+
elif name == "sit_test":
210+
result = Sit(arguments["package_path"]).test(
211+
run_actual=arguments.get("run_actual", False),
212+
runner=arguments.get("runner"),
213+
timeout=arguments.get("timeout", 30),
214+
)
215+
elif name == "sit_diff":
216+
result = Sit(arguments["new_path"]).diff(
217+
arguments["old_path"],
218+
include_text_diffs=arguments.get("include_text_diffs", False),
219+
)
220+
elif name == "sit_pr_summary":
221+
result = Sit(arguments["current_path"]).pr_summary(arguments["baseline_path"])
222+
elif name == "sit_report":
223+
result = Sit(arguments["package_path"]).report(
224+
compare=arguments.get("compare_path"),
225+
)
226+
elif name == "sit_doctor":
227+
result = Sit(arguments["package_path"]).doctor()
228+
else:
229+
return json.dumps({"error": f"Unknown tool: {name}"})
230+
return json.dumps(result, ensure_ascii=False, indent=2)
231+
except Exception as exc:
232+
return json.dumps({"error": f"{type(exc).__name__}: {exc}"})
233+
234+
235+
# ---------------------------------------------------------------------------
236+
# Server factory
237+
# ---------------------------------------------------------------------------
238+
239+
def create_server() -> Server:
240+
"""Create and configure an MCP Server instance with sit tools."""
241+
server = Server("sit-mcp-server")
242+
243+
@server.list_tools()
244+
async def list_tools() -> list[Tool]:
245+
return TOOLS
246+
247+
@server.call_tool()
248+
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
249+
result = _handle_tool(name, arguments)
250+
return [TextContent(type="text", text=result)]
251+
252+
return server
253+
254+
255+
# ---------------------------------------------------------------------------
256+
# Entry point
257+
# ---------------------------------------------------------------------------
258+
259+
async def _run_stdio() -> None:
260+
server = create_server()
261+
async with stdio_server() as (read_stream, write_stream):
262+
await server.run(read_stream, write_stream, server.create_initialization_options())
263+
264+
265+
def main() -> None:
266+
"""Run the sit MCP server over stdio transport."""
267+
import asyncio
268+
269+
asyncio.run(_run_stdio())
270+
271+
272+
if __name__ == "__main__":
273+
main()

0 commit comments

Comments
 (0)