Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions cli/azd/.vscode/cspell-azd-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ BUILDNUMBER
buildpacks
byoi
callstack
centralus
cflags
charmbracelet
circleci
Expand Down
1 change: 1 addition & 0 deletions cli/azd/cmd/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ func (a *mcpStartAction) Run(ctx context.Context) (*actions.ActionResult, error)
tools.NewAzdProjectValidationTool(),
tools.NewAzdYamlSchemaTool(),
tools.NewAzdErrorTroubleShootingTool(),
tools.NewAzdProvisionCommonErrorTool(),
}

allTools := []server.ServerTool{}
Expand Down
36 changes: 30 additions & 6 deletions cli/azd/cmd/middleware/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,12 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action
previousError = originalError
agentOutput, err := azdAgent.SendMessage(ctx, fmt.Sprintf(
`Steps to follow:
1. Use available tools to identify, explain and diagnose this error when running azd command and its root cause.
2. Only return a JSON object in the following format:
1. Check if the error is included in azd_provision_common_error tool.
If not, jump to step 2.
If so, jump to step 3 and only use the solution azd_provision_common_error provided.
2. Use available tools to identify, explain and diagnose this error when running azd command and its root cause.
3. Return ONLY the following JSON object as your final response. Do not add any text before or after.
Do not use markdown code blocks. Return raw JSON only:
{
"analysis": "Brief explanation of the error and its root cause",
"solutions": [
Expand All @@ -224,12 +228,19 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action
]
}
Provide up to 3 solutions. Each solution must be concise (one sentence).
IMPORTANT: Your response must be ONLY the JSON object above, nothing else.
Error details: %s`, errorInput))

// Extract solutions from agent output even if there's a parsing error
// The agent may return valid content
solutions := extractSuggestedSolutions(agentOutput)

// If no solutions found in output, try extracting from the error message
// LangChain may fail to parse but errors include the valid JSON
if len(solutions) == 0 && err != nil {
solutions = extractSuggestedSolutions(err.Error())
}

// Only fail if we got an error AND couldn't extract any solutions
if err != nil && len(solutions) == 0 {
e.displayAgentResponse(ctx, agentOutput, AIDisclaimer)
Expand All @@ -246,11 +257,14 @@ func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action
if continueWithFix {
agentOutput, err := azdAgent.SendMessage(ctx, fmt.Sprintf(
`Steps to follow:
1. Use available tools to identify, explain and diagnose this error when running azd command and its root cause.
2. Resolve the error by making the minimal, targeted change required to the code or configuration.
1. Check if the error is included in azd_provision_common_error tool.
If so, jump to step 3 and only use the solution azd_provision_common_error provided.
If not, continue to step 2.
2. Use available tools to identify, explain and diagnose this error when running azd command and its root cause.
3. Resolve the error by making the minimal, targeted change required to the code or configuration.
Avoid unnecessary modifications and focus only on what is essential to restore correct functionality.
3. Remove any changes that were created solely for validation and are not part of the actual error fix.
4. You are currently in the middle of executing '%s'. Never run this command.
4. Remove any changes that were created solely for validation and are not part of the actual error fix.
5. You are currently in the middle of executing '%s'. Never run this command.
Error details: %s`, e.options.CommandPath, errorInput))

if err != nil {
Expand Down Expand Up @@ -380,8 +394,18 @@ func promptForErrorHandlingConsent(

// extractSuggestedSolutions extracts solutions from the LLM response.
// It expects a JSON response with the structure: {"analysis": "...", "solutions": ["...", "...", "..."]}
// The response may be wrapped in a "text" field by the agent framework:
// {"text": "{\"analysis\": ..., \"solutions\": [...]}"}
// If JSON parsing fails, it returns an empty slice.
func extractSuggestedSolutions(llmResponse string) []string {
// First, check if response is wrapped in a "text" field (agent framework wrapper)
textResult := gjson.Get(llmResponse, "text")
if textResult.Exists() && textResult.Type == gjson.String {
// Unwrap the text field - it contains the actual JSON as a string
llmResponse = textResult.String()
}

// Now extract solutions from the unwrapped response
result := gjson.Get(llmResponse, "solutions")
if !result.Exists() {
return []string{}
Expand Down
49 changes: 49 additions & 0 deletions cli/azd/cmd/middleware/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,55 @@ func Test_ExtractSuggestedSolutions(t *testing.T) {
expectedCount: 2,
expectedFirst: "Fix the multi-line\nconfiguration issue",
},
{
name: "Agent Framework Wrapped Response - Text Field with JSON String",
llmResponse: `{"text": "{\"analysis\": \"Error analysis\", \"solutions\": [\"S1\", \"S2\", \"S3\"]}"}`,
expectedCount: 3,
expectedFirst: "S1",
},
{
name: "Agent Framework Wrapped Response - Text Field with Escaped JSON",
llmResponse: `{"text": "{\"analysis\": \"The deployment failed due to insufficient permissions\", ` +
`\"solutions\": [\"Grant Owner role to the user\", \"Use User Access Administrator role\", ` +
`\"Contact subscription admin\"]}"}`,
expectedCount: 3,
expectedFirst: "Grant Owner role to the user",
},
{
name: "Agent Framework Wrapped Response - Text Field with Single Solution",
llmResponse: `{"text": "{\"analysis\": \"Simple error\", \"solutions\": [\"Single fix\"]}"}`,
expectedCount: 1,
expectedFirst: "Single fix",
},
{
name: "Agent Framework Wrapped Response - Text Field with Empty Solutions",
llmResponse: `{"text": "{\"analysis\": \"Error with no solutions\", \"solutions\": []}"}`,
expectedCount: 0,
},
{
name: "Agent Framework Wrapped Response - Text Field Not a String",
llmResponse: `{"text": 12345}`,
expectedCount: 0,
},
{
name: "Agent Framework Wrapped Response - Text Field is Object Not String",
llmResponse: `{"text": {"analysis": "nested", "solutions": ["should not extract"]}}`,
expectedCount: 0,
},
{
name: "Agent Framework Wrapped Response - Text Field with Invalid Inner JSON",
llmResponse: `{"text": "this is not valid json inside"}`,
expectedCount: 0,
},
{
name: "Direct JSON Takes Precedence When Text Field Missing",
llmResponse: `{
"analysis": "Direct analysis",
"solutions": ["Direct solution 1", "Direct solution 2"]
}`,
expectedCount: 2,
expectedFirst: "Direct solution 1",
},
}

for _, tt := range tests {
Expand Down
43 changes: 43 additions & 0 deletions cli/azd/internal/mcp/tools/azd_provision_common_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package tools

import (
"context"

"github.com/azure/azure-dev/cli/azd/internal/mcp/tools/prompts"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)

// NewAzdProvisionCommonErrorTool creates a new azd provision common error tool
func NewAzdProvisionCommonErrorTool() server.ServerTool {
return server.ServerTool{
Tool: mcp.NewTool(
"provision_common_error",
mcp.WithReadOnlyHintAnnotation(true),
mcp.WithIdempotentHintAnnotation(true),
mcp.WithDestructiveHintAnnotation(false),
mcp.WithOpenWorldHintAnnotation(false),
mcp.WithDescription(
`Returns specific troubleshooting instructions for common Azure Developer CLI (azd) provisioning errors.

Use this tool when azd commands fail with:
- Service unavailability errors (quota/capacity issues)
- Authorization failures for role assignments
- Role assignment conflicts (already exists)
- Location offer restricted errors for Azure Database for PostgreSQL
- VM quota exceeded errors when provisioning compute resources
- Cognitive Services account provisioning state invalid errors

Provides step-by-step diagnostic instructions for the LLM agent to execute.`,
),
),
Handler: handleAzdProvisionCommonError,
}
}

func handleAzdProvisionCommonError(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return mcp.NewToolResultText(prompts.AzdProvisionCommonErrorPrompt), nil
}
Loading
Loading