Skip to content

Commit f336e8f

Browse files
authored
[mcp] tool changes (#41409)
* splitting out tool changes * update isnt * updating but still not running tox * add path
1 parent 9a3b2b3 commit f336e8f

File tree

6 files changed

+1391
-17
lines changed

6 files changed

+1391
-17
lines changed

.github/copilot-instructions.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414

1515
### RULE 3: VERIFY ENVIRONMENT FIRST
1616
**BEFORE any commands:**
17-
1. Use `verify_setup` tool from azure-sdk-validation server
18-
2. Ensure Python virtual environment is active
17+
1. Get path to azure-sdk-for-python repo root, and path to tox.ini file
18+
2. Use `verify_setup` tool from azure-sdk-python-mcp server
19+
3. Ensure Python virtual environment is active
1920

2021
**Virtual Environment Setup:**
2122
```bash
@@ -80,15 +81,15 @@ curl -s "https://api.github.com/repos/Azure/azure-rest-api-specs/commits?path=<p
8081

8182
### STEP 1: ENVIRONMENT VERIFICATION
8283
```
83-
ACTION: Run verify_setup tool
84+
ACTION: Run verify_setup mcp tool
8485
IF missing dependencies:
8586
STOP and install missing dependencies
8687
THEN proceed to Step 2
8788
```
8889

8990
### STEP 2: SDK GENERATION
9091
```
91-
ACTION: Use typespec-python mcp server tools
92+
ACTION: Use azure-sdk-python-mcp sdk generation server tools (init, init_local)
9293
TIMING: ALWAYS inform user before starting: "This SDK generation step will take approximately 5-6 minutes to complete."
9394
IF local path provided:
9495
USE local mcp tools with tspconfig.yaml path
@@ -101,7 +102,7 @@ IF commands fail:
101102
```
102103
TIMING: Inform user: "Static validation will take approximately 3-5 minutes for each step."
103104
FOR EACH validation step:
104-
RUN validation
105+
RUN validation (tox mcp tool)
105106
IF errors/warnings found:
106107
FIX issues
107108
RERUN same step
@@ -221,4 +222,5 @@ tox -e pylint --c <path_to_tox.ini> --root .
221222

222223
**REQUIREMENTS:**
223224
- Use Python 3.9 compatible environment
224-
- Follow official fixing guidelines
225+
- Follow official fixing guidelines
226+
- Use tox mcp tool for running MyPy

.vscode/mcp.json

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
{
22
"servers": {
3-
"typespec-python": {
3+
"azure-sdk-python-mcp": {
44
"command": "uv",
55
"args": [
66
"--directory",
7-
"${workspaceFolder}/tools/mcp/typespec/",
7+
"${workspaceFolder}/tools/mcp/azure-sdk-python-mcp/",
88
"run",
99
"main.py"
1010
],
1111
},
12-
"azure-sdk-validation": {
13-
"command": "uv",
14-
"args": [
15-
"--directory",
16-
"${workspaceFolder}/tools/mcp/validation/",
17-
"run",
18-
"main.py"
19-
]
20-
},
2112
}
2213
}

tools/mcp/azure-sdk-python-mcp/README.md

Whitespace-only changes.
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
from mcp.server.fastmcp import FastMCP
2+
import subprocess
3+
from typing import Dict, List, Optional, Any
4+
import logging
5+
import sys
6+
import os
7+
import re
8+
from github import Github
9+
10+
# Create FastMCP instance
11+
mcp = FastMCP("azure-sdk-python-mcp")
12+
13+
# Setup logging
14+
logger = logging.getLogger()
15+
logger.setLevel(logging.INFO)
16+
handler = logging.StreamHandler(sys.stderr)
17+
logger.addHandler(handler)
18+
19+
# Log environment information
20+
logger.info(f"Running with Python executable: {sys.executable}")
21+
logger.info(f"Virtual environment path: {os.environ.get('VIRTUAL_ENV', 'Not running in a virtual environment')}")
22+
logger.info(f"Working directory: {os.getcwd()}")
23+
24+
def get_latest_commit(tspurl: str) -> str:
25+
"""Get the latest commit hash for a given TypeSpec config URL.
26+
27+
Args:
28+
tspurl: The URL to the tspconfig.yaml file.
29+
30+
Returns:
31+
The URL with the latest commit hash for the specified TypeSpec configuration.
32+
"""
33+
try:
34+
# Extract URL components using regex
35+
res = re.match(
36+
r"^https://(?P<urlRoot>github|raw.githubusercontent).com/(?P<repo>[^/]*/azure-rest-api-specs(-pr)?)/(tree/|blob/)?(?P<commit>[0-9a-f]{40}|[^/]+)/(?P<path>.*)/tspconfig.yaml$",
37+
tspurl
38+
)
39+
40+
if res is None:
41+
raise ValueError(f"Invalid TypeSpec URL format: {tspurl}")
42+
43+
groups = res.groupdict()
44+
commit = groups["commit"]
45+
46+
# Parse repository information
47+
repo_parts = tspurl.split("/")
48+
repo_name = f"{repo_parts[3]}/{repo_parts[4]}"
49+
50+
# Get path within repo
51+
parts = tspurl.split("azure-rest-api-specs/blob/")[1].split("/")
52+
parts.pop(0) # Remove branch name
53+
folder_path = "/".join(parts)
54+
55+
# If commit is a branch name (not a SHA), get latest commit
56+
if not commit or commit == "main" or len(commit) != 40:
57+
g = Github()
58+
repo = g.get_repo(repo_name)
59+
commits = repo.get_commits(path=folder_path)
60+
61+
if not commits:
62+
raise ValueError(f"No commits found for path: {folder_path}")
63+
64+
latest_commit = commits[0].sha
65+
return f"https://raw.githubusercontent.com/{groups['repo']}/{latest_commit}/{groups['path']}/tspconfig.yaml"
66+
67+
return f"https://raw.githubusercontent.com/{groups['repo']}/{commit}/{groups['path']}/tspconfig.yaml"
68+
69+
except Exception as e:
70+
logger.error(f"Error getting latest commit: {str(e)}")
71+
raise
72+
73+
def run_command(command: List[str], cwd: str, is_typespec: bool = False,
74+
typespec_args: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
75+
"""Run a command and return the result.
76+
77+
Args:
78+
command: The command to run as a list of strings
79+
cwd: Optional working directory to run the command in
80+
is_typespec: Whether this is a TypeSpec CLI command
81+
typespec_args: Optional arguments for TypeSpec commands
82+
83+
Returns:
84+
Dictionary with command execution results
85+
"""
86+
# Handle TypeSpec commands
87+
if is_typespec:
88+
# Build TypeSpec CLI command
89+
if os.name == "nt": # Windows
90+
cli_cmd = ["cmd.exe", "/C", "npx", "@azure-tools/typespec-client-generator-cli"] + command
91+
else: # Unix/Linux/MacOS
92+
cli_cmd = ["npx", "@azure-tools/typespec-client-generator-cli"] + command
93+
94+
# Add TypeSpec arguments
95+
if typespec_args:
96+
for key, value in typespec_args.items():
97+
cli_cmd.append(f"--{key}")
98+
cli_cmd.append(str(value))
99+
100+
command = cli_cmd
101+
# Log the command
102+
logger.info(f"Running command: {' '.join(command)}")
103+
104+
try:
105+
logger.info(f"Using repository root as working directory: {cwd}")
106+
107+
# Handle Windows command prefix if not a TypeSpec command
108+
if os.name == "nt" and not is_typespec:
109+
command = ["cmd.exe", "/C"] + command
110+
111+
# Execute command
112+
result = subprocess.run(
113+
command,
114+
capture_output=True,
115+
text=True,
116+
cwd=cwd,
117+
stdin=subprocess.DEVNULL,
118+
)
119+
120+
if result.stdout:
121+
logger.info(f"Command output excerpt: {result.stdout[:100]}...")
122+
123+
return {
124+
"success": result.returncode == 0,
125+
"stdout": result.stdout,
126+
"stderr": result.stderr,
127+
"code": result.returncode
128+
}
129+
except Exception as e:
130+
logger.error(f"Error running command: {e}")
131+
return {
132+
"success": False,
133+
"stdout": "",
134+
"stderr": str(e),
135+
"code": 1,
136+
}
137+
138+
@mcp.tool("verify_setup")
139+
def verify_setup_tool(command_path: str, tox_ini_path: str) -> Dict[str, Any]:
140+
"""Verify machine is set up correctly for development.
141+
142+
:param str command_path: Path to the command in. (i.e. ./azure-sdk-for-python/)
143+
:param str tox_ini_path: Path to the tox.ini file. (i.e. ./azure-sdk-for-python/eng/tox/tox.ini)
144+
"""
145+
def verify_installation(command: List[str], name: str) -> Dict[str, Any]:
146+
"""Helper function to verify installation of a tool."""
147+
logger.info(f"Checking installation of {name}")
148+
result = run_command(command, cwd=command_path)
149+
150+
if not result["success"]:
151+
return {
152+
"success": False,
153+
"message": f"{name} is not installed or not available in PATH.",
154+
"details": {
155+
"stdout": result["stdout"],
156+
"stderr": result["stderr"],
157+
"exit_code": result["code"]
158+
}
159+
}
160+
161+
version_output = result["stdout"].strip() or "No version output"
162+
return {
163+
"success": True,
164+
"message": f"{name} is installed. Version: {version_output}"
165+
}
166+
167+
# Verify required tools
168+
results = {
169+
"node": verify_installation(["node", "--version"], "Node.js"),
170+
"python": verify_installation(["python", "--version"], "Python"),
171+
"tox": verify_installation(["tox", "--version", "-c", tox_ini_path], "tox")
172+
}
173+
174+
return results
175+
176+
@mcp.tool("validation_tool")
177+
def tox_tool(package_path: str, environment: str, repo_path: str, tox_ini_path: str) -> Dict[str, Any]:
178+
"""Run validation steps on a Python package using tox.
179+
180+
Args:
181+
package_path: Path to the Python package to test
182+
environment: tox environment to run (e.g., 'pylint', 'mypy')
183+
repo_path: Path to the repository root (i.e. ./azure-sdk-for-python/)
184+
tox_ini_path: Path to the tox.ini file (i.e. ./azure-sdk-for-python/eng/tox/tox.ini)
185+
"""
186+
# Build and run tox command
187+
command = ["tox", "run", "-e", environment, "-c", tox_ini_path, "--root", package_path]
188+
return run_command(command, cwd=repo_path)
189+
190+
@mcp.tool("init")
191+
def init_tool(tsp_config_url: str, repo_path: str) -> Dict[str, Any]:
192+
"""Initializes and generates a typespec client library directory given the url.
193+
194+
Args:
195+
tsp_config_url: The URL to the tspconfig.yaml file.
196+
repo_path: The path to the repository root (i.e. ./azure-sdk-for-python/).
197+
Returns:
198+
A dictionary containing the result of the command.
199+
"""
200+
try:
201+
# Get updated URL with latest commit hash
202+
updated_url = get_latest_commit(tsp_config_url)
203+
204+
# Run the init command using the combined function
205+
return run_command(["init"], cwd=repo_path, is_typespec=True,
206+
typespec_args={"tsp-config": updated_url})
207+
208+
except RuntimeError as e:
209+
return {
210+
"success": False,
211+
"message": str(e),
212+
"stdout": "",
213+
"stderr": "",
214+
"code": 1
215+
}
216+
217+
@mcp.tool("init_local")
218+
def init_local_tool(tsp_config_path: str, repo_path:str) -> Dict[str, Any]:
219+
"""Initializes and subsequently generates a typespec client library directory from a local azure-rest-api-specs repo.
220+
221+
This command is used to generate a client library from a local azure-rest-api-specs repository. No additional
222+
commands are needed to generate the client library.
223+
224+
Args:
225+
tsp_config_path: The path to the local tspconfig.yaml file.
226+
repo_path: The path to the repository root (i.e. ./azure-sdk-for-python/).
227+
228+
Returns:
229+
A dictionary containing the result of the command. """
230+
try:
231+
232+
# Run the init command with local path using the combined function
233+
return run_command(["init"], cwd=repo_path, is_typespec=True,
234+
typespec_args={"tsp-config": tsp_config_path})
235+
236+
except RuntimeError as e:
237+
return {
238+
"success": False,
239+
"message": str(e),
240+
"stdout": "",
241+
"stderr": "",
242+
"code": 1
243+
}
244+
245+
# Run the MCP server
246+
if __name__ == "__main__":
247+
mcp.run(transport='stdio')
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[project]
2+
name = "azure-sdk-python-mcp"
3+
version = "0.1.0"
4+
description = "Add your description here"
5+
readme = "README.md"
6+
requires-python = ">=3.10"
7+
dependencies = [
8+
"azure-core>=1.34.0",
9+
"black>=25.1.0",
10+
"mcp[cli]>=1.9.2",
11+
"mypy>=1.16.0",
12+
"pip>=25.1.1",
13+
"pygithub>=2.6.1",
14+
"pylint>=3.3.7",
15+
"tox>=4.26.0",
16+
]

0 commit comments

Comments
 (0)