Skip to content

Commit d3f8a5f

Browse files
authored
Merge pull request #60 from Azure-Samples/antchu/mcp_tool_trigger
feat: use `mcp_tool_trigger` and simplify tool property creation
2 parents 498ee7a + 4fc97bf commit d3f8a5f

File tree

5 files changed

+101
-79
lines changed

5 files changed

+101
-79
lines changed

scripts/generate-settings.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ $settingsJson = @"
2424
"AGENTS_MODEL_DEPLOYMENT_NAME": "gpt-4o-mini",
2525
"COSMOS_ENDPOINT": "$cosmosEndpoint",
2626
"AZURE_OPENAI_ENDPOINT": "$azureOpenAIEndpoint",
27-
"PROJECT_ENDPOINT": "$projectEndpoint"
27+
"PROJECT_ENDPOINT": "$projectEndpoint",
28+
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1"
2829
}
2930
}
3031
"@

scripts/generate-settings.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ cat > src/local.settings.json << EOF
2323
"AGENTS_MODEL_DEPLOYMENT_NAME": "gpt-4o-mini",
2424
"COSMOS_ENDPOINT": "$COSMOS_ENDPOINT",
2525
"AZURE_OPENAI_ENDPOINT": "$AZURE_OPENAI_ENDPOINT",
26-
"PROJECT_ENDPOINT": "$PROJECT_ENDPOINT"
26+
"PROJECT_ENDPOINT": "$PROJECT_ENDPOINT",
27+
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1"
2728
}
2829
}
2930
EOF

src/function_app.py

Lines changed: 42 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@
2828
import azure.functions as func
2929
from data import cosmos_ops # Module for Cosmos DB operations
3030
from agents import deep_wiki, code_style # Modules for AI agent operations
31+
from tool_helpers import ToolProperty, ToolPropertyList # Helper classes for tool definitions
3132

3233
# Initialize the Azure Functions app
3334
# This is the main entry point for all function definitions
3435
app = func.FunctionApp()
3536

3637
# =============================================================================
37-
# CONSTANTS AND UTILITY CLASSES
38+
# CONSTANTS
3839
# =============================================================================
3940

4041
# Constants for input property names in MCP tool definitions
@@ -45,75 +46,41 @@
4546
_CHAT_HISTORY_PROPERTY_NAME = "chathistory" # Property name for previous chat context
4647
_USER_QUERY_PROPERTY_NAME = "userquery" # Property name for the user's specific question
4748

48-
# Utility class to define properties for MCP tools
49-
# This creates a standardized way to document and validate expected inputs
50-
class ToolProperty:
51-
"""
52-
Defines a property for an MCP tool, including its name, data type, and description.
53-
54-
These properties are used by AI assistants (like GitHub Copilot) to understand:
55-
- What inputs each tool expects
56-
- What data types those inputs should be
57-
- How to describe each input to users
58-
59-
This helps the AI to correctly invoke the tool with appropriate parameters.
60-
"""
61-
def __init__(self, property_name: str, property_type: str, description: str):
62-
self.propertyName = property_name # Name of the property
63-
self.propertyType = property_type # Data type (string, number, etc.)
64-
self.description = description # Human-readable description
65-
66-
def to_dict(self):
67-
"""
68-
Converts the property definition to a dictionary format for JSON serialization.
69-
Required for MCP tool registration.
70-
"""
71-
return {
72-
"propertyName": self.propertyName,
73-
"propertyType": self.propertyType,
74-
"description": self.description,
75-
}
76-
7749
# =============================================================================
7850
# TOOL PROPERTY DEFINITIONS
7951
# =============================================================================
80-
# Each MCP tool needs a schema definition to describe its expected inputs
81-
# This is how AI assistants know what parameters to provide when using these tools
52+
# Each MCP tool needs a schema definition to describe its expected inputs.
53+
# This is how AI assistants know what parameters to provide when using these tools.
54+
# Tool properties are passed to the @app.mcp_tool_trigger decorator via
55+
# the `tool_properties` parameter (as a JSON-serialized string).
8256

8357
# Properties for the save_snippet tool
8458
# This tool saves code snippets with their vector embeddings
85-
tool_properties_save_snippets = [
59+
tool_properties_save_snippets = ToolPropertyList(
8660
ToolProperty(_SNIPPET_NAME_PROPERTY_NAME, "string", "A unique name or identifier for the code snippet. Provide this if you have a specific name for the snippet being saved. Essential for identifying the snippet later."),
8761
ToolProperty(_PROJECT_ID_PROPERTY_NAME, "string", "An identifier for a project to associate this snippet with. Useful for organizing snippets. If omitted or not relevant, it defaults to 'default-project'."),
8862
ToolProperty(_SNIPPET_PROPERTY_NAME, "string", "The actual code or text content of the snippet. Provide the content that needs to be saved and made searchable."),
89-
]
63+
)
9064

9165
# Properties for the get_snippet tool
9266
# This tool retrieves previously saved snippets by name
93-
tool_properties_get_snippets = [
67+
tool_properties_get_snippets = ToolPropertyList(
9468
ToolProperty(_SNIPPET_NAME_PROPERTY_NAME, "string", "The unique name or identifier of the code snippet you want to retrieve. This is required to fetch a specific snippet."),
95-
]
69+
)
9670

9771
# Properties for the deep_wiki tool
9872
# This tool generates comprehensive documentation from code snippets
99-
tool_properties_wiki = [
73+
tool_properties_wiki = ToolPropertyList(
10074
ToolProperty(_CHAT_HISTORY_PROPERTY_NAME, "string", "Optional. The preceding conversation history (e.g., user prompts and AI responses). Providing this helps contextualize the wiki content generation. Omit if no relevant history exists or if a general wiki is desired."),
10175
ToolProperty(_USER_QUERY_PROPERTY_NAME, "string", "Optional. The user's specific question, instruction, or topic to focus the wiki documentation on. If omitted, a general wiki covering available snippets might be generated."),
102-
]
76+
)
10377

10478
# Properties for the code_style tool
10579
# This tool generates coding style guides based on existing snippets
106-
tool_properties_code_style = [
80+
tool_properties_code_style = ToolPropertyList(
10781
ToolProperty(_CHAT_HISTORY_PROPERTY_NAME, "string", "Optional. The preceding conversation history (e.g., user prompts and AI responses). This can provide context for the code style analysis or guide generation. Omit if not available or not relevant."),
10882
ToolProperty(_USER_QUERY_PROPERTY_NAME, "string", "Optional. The user's specific question, instruction, or prompt related to code style. If omitted, a general code style analysis or a default guide might be generated."),
109-
]
110-
111-
# Convert tool properties to JSON for MCP tool registration
112-
# This is required format for the MCP tool trigger binding
113-
tool_properties_save_snippets_json = json.dumps([prop.to_dict() for prop in tool_properties_save_snippets])
114-
tool_properties_get_snippets_json = json.dumps([prop.to_dict() for prop in tool_properties_get_snippets])
115-
tool_properties_wiki_json = json.dumps([prop.to_dict() for prop in tool_properties_wiki])
116-
tool_properties_code_style_json = json.dumps([prop.to_dict() for prop in tool_properties_code_style])
83+
)
11784

11885
# =============================================================================
11986
# SAVE SNIPPET FUNCTIONALITY
@@ -133,10 +100,10 @@ async def http_save_snippet(req: func.HttpRequest, embeddings: str) -> func.Http
133100
- Stores the snippet and its embedding in Cosmos DB
134101
135102
The @app.embeddings_input decorator:
136-
- Automatically calls Azure OpenAI before the function runs
103+
- Automatically calls Azure OpenAI to generate embeddings before the function runs
137104
- Extracts 'code' from the request body
138105
- Generates a vector embedding for that code
139-
- Provides the embedding to the function via the 'embeddings' parameter
106+
- Passes the embedding to the function via the 'embeddings' parameter
140107
"""
141108
try:
142109
# 1. Extract and validate the request body
@@ -192,12 +159,11 @@ async def http_save_snippet(req: func.HttpRequest, embeddings: str) -> func.Http
192159

193160
# MCP tool for saving snippets
194161
# This is accessible to AI assistants via the MCP protocol
195-
@app.generic_trigger(
162+
@app.mcp_tool_trigger(
196163
arg_name="context",
197-
type="mcpToolTrigger",
198-
toolName="save_snippet",
164+
tool_name="save_snippet",
199165
description="Saves a given code snippet. It can take a snippet name, the snippet content, and an optional project ID. Embeddings are generated for the content to enable semantic search. The LLM should provide 'snippetname' and 'snippet' when intending to save.",
200-
toolProperties=tool_properties_save_snippets_json,
166+
tool_properties=tool_properties_save_snippets.to_json(),
201167
)
202168
@app.embeddings_input(arg_name="embeddings", input="{arguments.snippet}", input_type="rawText", embeddings_model="%EMBEDDING_MODEL_DEPLOYMENT_NAME%")
203169
async def mcp_save_snippet(context: str, embeddings: str) -> str:
@@ -210,10 +176,10 @@ async def mcp_save_snippet(context: str, embeddings: str) -> str:
210176
- Shares the same storage logic with the HTTP endpoint
211177
212178
The difference from the HTTP endpoint:
213-
- Receives parameters via the 'context' JSON string instead of HTTP body
214-
- Returns results as a JSON string instead of an HTTP response
215-
- Uses {arguments.snippet} in the embeddings_input decorator to reference
216-
the snippet content from the context arguments
179+
- Receives parameters via the 'context' JSON string (containing 'arguments') instead of HTTP body
180+
- Returns results as a JSON string instead of an HTTP response object
181+
- Uses {arguments.snippet} in the @app.embeddings_input decorator to reference
182+
the snippet content from the context's 'arguments' object
217183
"""
218184
try:
219185
# 1. Parse the context JSON string to extract the arguments
@@ -301,12 +267,11 @@ async def http_get_snippet(req: func.HttpRequest) -> func.HttpResponse:
301267

302268
# MCP tool for retrieving snippets
303269
# This is accessible to AI assistants via the MCP protocol
304-
@app.generic_trigger(
270+
@app.mcp_tool_trigger(
305271
arg_name="context",
306-
type="mcpToolTrigger",
307-
toolName="get_snippet",
272+
tool_name="get_snippet",
308273
description="Retrieves a previously saved code snippet using its unique name. The LLM should provide the 'snippetname' when it intends to fetch a specific snippet.",
309-
toolProperties=tool_properties_get_snippets_json,
274+
tool_properties=tool_properties_get_snippets.to_json(),
310275
)
311276
async def mcp_get_snippet(context) -> str:
312277
"""
@@ -318,8 +283,9 @@ async def mcp_get_snippet(context) -> str:
318283
- Returns the snippet as a JSON string
319284
320285
The difference from the HTTP endpoint:
321-
- Receives the snippet name via the 'context' JSON string instead of URL path
322-
- Returns results as a JSON string instead of an HTTP response
286+
- Receives the snippet name via the 'context' JSON string (containing 'arguments')
287+
instead of from the URL path parameter
288+
- Returns results as a JSON string instead of an HTTP response object
323289
"""
324290
try:
325291
# 1. Parse the context JSON string to extract the arguments
@@ -400,12 +366,11 @@ async def http_code_style(req: func.HttpRequest) -> func.HttpResponse:
400366

401367
# MCP tool for generating code style guides
402368
# This is accessible to AI assistants via the MCP protocol
403-
@app.generic_trigger(
369+
@app.mcp_tool_trigger(
404370
arg_name="context",
405-
type="mcpToolTrigger",
406-
toolName="code_style",
371+
tool_name="code_style",
407372
description="Generates a code style guide. This involves creating content for a new file (e.g., 'code-style-guide.md' to be placed in the workspace root). Optional 'chathistory' and 'userquery' can be supplied to customize or focus the guide; omit them for a general or default style guide.",
408-
toolProperties=tool_properties_code_style_json,
373+
tool_properties=tool_properties_code_style.to_json(),
409374
)
410375
async def mcp_code_style(context) -> str:
411376
"""
@@ -417,8 +382,9 @@ async def mcp_code_style(context) -> str:
417382
- Returns the generated content as a JSON string
418383
419384
The difference from the HTTP endpoint:
420-
- Receives parameters via the 'context' JSON string instead of HTTP body
421-
- Returns results as a JSON string instead of an HTTP response
385+
- Receives parameters via the 'context' JSON string (containing 'arguments')
386+
instead of HTTP body
387+
- Returns results as a JSON string instead of an HTTP response object
422388
"""
423389
try:
424390
logging.info("MCP: Starting code style content generation")
@@ -496,12 +462,11 @@ async def http_deep_wiki(req: func.HttpRequest) -> func.HttpResponse:
496462

497463
# MCP tool for generating comprehensive wiki documentation
498464
# This is accessible to AI assistants via the MCP protocol
499-
@app.generic_trigger(
465+
@app.mcp_tool_trigger(
500466
arg_name="context",
501-
type="mcpToolTrigger",
502-
toolName="deep_wiki",
467+
tool_name="deep_wiki",
503468
description="Creates comprehensive 'deep wiki' documentation. This involves generating content for a new wiki file (e.g., 'deep-wiki.md' to be placed in the workspace root), often by analyzing existing code snippets. Optional 'chathistory' and 'userquery' can be provided to refine or focus the wiki content; omit them for a general wiki.",
504-
toolProperties=tool_properties_wiki_json,
469+
tool_properties=tool_properties_wiki.to_json(),
505470
)
506471
async def mcp_deep_wiki(context) -> str:
507472
"""
@@ -513,9 +478,10 @@ async def mcp_deep_wiki(context) -> str:
513478
- Returns the generated content as a markdown string
514479
515480
The difference from the HTTP endpoint:
516-
- Receives parameters via the 'context' JSON string instead of HTTP body
517-
- Returns results directly as a markdown string, not wrapped in JSON
518-
(This is an exception to the usual pattern of returning JSON)
481+
- Receives parameters via the 'context' JSON string (containing 'arguments')
482+
instead of HTTP body
483+
- Returns the raw markdown string directly instead of an HTTP response object
484+
(This is an exception to the usual pattern of returning JSON-wrapped results)
519485
"""
520486
try:
521487
logging.info("MCP: Starting deep wiki content generation")

src/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Azure Functions runtime and core dependencies
2-
azure-functions
2+
azure-functions>=1.24.0
33

44
# Azure service SDKs
55
azure-storage-blob # For blob storage operations

src/tool_helpers.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import json
2+
3+
class ToolProperty:
4+
"""
5+
Defines a property for an MCP tool, including its name, data type, and description.
6+
7+
These properties are used by AI assistants (like GitHub Copilot) to understand:
8+
- What inputs each tool expects
9+
- What data types those inputs should be
10+
- How to describe each input to users
11+
12+
This helps the AI to correctly invoke the tool with appropriate parameters.
13+
"""
14+
def __init__(self, property_name: str, property_type: str, description: str):
15+
self.propertyName = property_name # Name of the property
16+
self.propertyType = property_type # Data type (string, number, etc.)
17+
self.description = description # Human-readable description
18+
19+
def to_dict(self) -> dict:
20+
"""
21+
Converts the property definition to a dictionary format for JSON serialization.
22+
Required for MCP tool registration.
23+
"""
24+
return {
25+
"propertyName": self.propertyName,
26+
"propertyType": self.propertyType,
27+
"description": self.description,
28+
}
29+
30+
31+
class ToolPropertyList:
32+
"""
33+
Manages a collection of ToolProperty objects and provides JSON serialization.
34+
35+
Simplifies creating and serializing lists of tool properties for MCP tool registration.
36+
"""
37+
def __init__(self, *properties: ToolProperty):
38+
"""
39+
Initialize with zero or more ToolProperty instances.
40+
41+
Args:
42+
*properties: Variable number of ToolProperty objects.
43+
"""
44+
self.properties = list(properties)
45+
46+
def to_json(self):
47+
"""
48+
Returns a JSON string representation of the property list.
49+
50+
Returns:
51+
JSON string containing the serialized properties.
52+
"""
53+
return json.dumps([prop.to_dict() for prop in self.properties])
54+

0 commit comments

Comments
 (0)