-
Notifications
You must be signed in to change notification settings - Fork 53
Description
The PowerShell worker only sets function invocation results under a limited set of circumstances. This seems to be captured in BindOutputFromResult
:
azure-functions-powershell-worker/src/RequestProcessor.cs
Lines 517 to 549 in bd1bfa6
private static void BindOutputFromResult(InvocationResponse response, AzFunctionInfo functionInfo, IDictionary results) | |
{ | |
if (functionInfo.DurableFunctionInfo.Type != DurableFunctionType.OrchestrationFunction) | |
{ | |
// Set out binding data and return response to be sent back to host | |
foreach (var (bindingName, bindingInfo) in functionInfo.OutputBindings) | |
{ | |
var outValue = results[bindingName]; | |
var transformedValue = Utils.TransformOutBindingValueAsNeeded(bindingName, bindingInfo, outValue); | |
var dataToUse = transformedValue.ToTypedData(); | |
// if one of the bindings is '$return' we need to set the ReturnValue | |
if (string.Equals(bindingName, AzFunctionInfo.DollarReturn, StringComparison.OrdinalIgnoreCase)) | |
{ | |
response.ReturnValue = dataToUse; | |
continue; | |
} | |
var paramBinding = new ParameterBinding() | |
{ | |
Name = bindingName, | |
Data = dataToUse | |
}; | |
response.OutputData.Add(paramBinding); | |
} | |
} | |
if (functionInfo.DurableFunctionInfo.ProvidesForcedDollarReturnValue || FunctionInfoUtilities.hasAssistantSkillTrigger(functionInfo)) | |
{ | |
response.ReturnValue = results[AzFunctionInfo.DollarReturn].ToTypedData(isDurableData: true); | |
} | |
} |
The result shouldn't be tied to an output binding explicitly. We do not want to introduce additional output bindings to reflect trigger results when needed. An example of this in action is the MCP extension. The MCP tool trigger is not available for PowerShell because the tool invocation response cannot be set, and we need a way to process the result of the invocation.
I imagine there are a few ways to solve this. I'm imagining some kind of Push-FunctionResult
or similar would be the best way forward. Mechanically, I think implicit honoring of $return
would work, but it feels weird to call Push-OutputBinding
in that context. If something like Push-FunctionResult
were to be introduced, I'd imagine it would need to be mutually exclusive with $return
.
Additional details about current behavior
Example: Using $return
with no binding
I attempted to create the following function:
param($ToolInvocationContext)
Write-Host "PowerShell MCP Tool trigger function processed a request."
$name = $ToolInvocationContext["arguments"]["name"] ?? "world"
Push-OutputBinding -Name "`$return" -Value "Hello, $name! This MCP tool executed successfully."
With the corresponding function.json
:
{
"bindings": [
{
"type": "mcpToolTrigger",
"direction": "in",
"name": "ToolInvocationContext",
"toolName": "SayHelloIdeal",
"description": "Responds to the user with a hello message.",
"toolProperties": "[{\"propertyName\":\"name\",\"propertyType\":\"string\",\"description\":\"The name of the person to greet.\"}]"
}
]
}
This appears to leadto no result being set. The tool doesn't do what it's supposed to. But in logs, I also see an error:
Exception :
Type : System.InvalidOperationException
TargetSite :
Name : ThrowTerminatingError
DeclaringType : [System.Management.Automation.MshCommandRuntime]
MemberType : Method
Module : System.Management.Automation.dll
Message : The specified name '$return' cannot be resolved to a valid output binding of this function.
Source : System.Management.Automation
HResult : -2146233079
StackTrace :
at System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecord errorRecord)
TargetObject : $return
CategoryInfo : InvalidOperation: ($return:String) [Push-OutputBinding], InvalidOperationException
FullyQualifiedErrorId : BindingNameNotExist,Microsoft.Azure.Functions.PowerShellWorker.Commands.PushOutputBindingCommand
Example: using a placeholder output binding
With the same function code as the previous example, I added a queue output binding, leading to the following function.json:
{
"bindings": [
{
"type": "mcpToolTrigger",
"direction": "in",
"name": "ToolInvocationContext",
"toolName": "SayHelloQueue",
"description": "Responds to the user with a hello message.",
"toolProperties": "[{\"propertyName\":\"name\",\"propertyType\":\"string\",\"description\":\"The name of the person to greet.\"}]"
},
{
"name": "$return",
"direction": "out",
"type": "queue",
"queueName": "mcp-tool-output"
}
]
}
In this case, the result will be set, leading to an MCP response as desired, but it unfortunately puts extra data into a queue unnecessarily.
Example: attempting to use the HTTP output binding
To avoid the unnecessary persistence, I tried using the HTTP output binding just for setting the result. Unsurprisingly, that didn't work, and it's conceptually awkward, but I include it here for completeness.
Here's the function.json
:
{
"bindings": [
{
"type": "mcpToolTrigger",
"direction": "in",
"name": "ToolInvocationContext",
"toolName": "SayHelloHttp",
"description": "Responds to the user with a hello message.",
"toolProperties": "[{\"propertyName\":\"name\",\"propertyType\":\"string\",\"description\":\"The name of the person to greet.\"}]"
},
{
"name": "$return",
"direction": "out",
"type": "http"
}
]
}
If I'd used the same code as in the previous examples, I would get the following exception, since the PowerShell worker expects a particular shape for HTTP outputs:
System.Private.CoreLib: Exception while executing function: Functions.HttpApproach. System.Private.CoreLib: Result: Failure
Exception: The given value for the 'http' output binding '$return' cannot be converted to the type 'HttpResponseContext'. The conversion failed with the following error: Cannot convert the "Hello, GitHub Copilot! This MCP tool executed successfully." value of type "System.String" to type "Microsoft.Azure.Functions.PowerShellWorker.HttpResponseContext".
Stack: at Microsoft.Azure.Functions.PowerShellWorker.Utility.Utils.TransformOutBindingValueAsNeeded(String bindingName, ReadOnlyBindingInfo bindingInfo, Object value) in D:\a_work\1\s\src\Utility\Utils.cs:line 209
at Microsoft.Azure.Functions.PowerShellWorker.RequestProcessor.BindOutputFromResult(InvocationResponse response, AzFunctionInfo functionInfo, IDictionary results) in D:\a_work\1\s\src\RequestProcessor.cs:line 525
at Microsoft.Azure.Functions.PowerShellWorker.RequestProcessor.ProcessInvocationRequestImpl(StreamingMessage request, AzFunctionInfo functionInfo, PowerShellManager psManager, FunctionInvocationPerformanceStopwatch stopwatch) in D:\a_work\1\s\src\RequestProcessor.cs:line 342.
Error processing request: '3' for client '3vn8i1d36tzmmhcc'
Microsoft.Azure.Functions.Extensions.Mcp: Method not found. System.Private.CoreLib: Exception while executing function: Functions.HttpApproach. System.Private.CoreLib: Result: Failure
Exception: The given value for the 'http' output binding '$return' cannot be converted to the type 'HttpResponseContext'. The conversion failed with the following error: Cannot convert the "Hello, GitHub Copilot! This MCP tool executed successfully." value of type "System.String" to type "Microsoft.Azure.Functions.PowerShellWorker.HttpResponseContext".
Stack: at Microsoft.Azure.Functions.PowerShellWorker.Utility.Utils.TransformOutBindingValueAsNeeded(String bindingName, ReadOnlyBindingInfo bindingInfo, Object value) in D:\a_work\1\s\src\Utility\Utils.cs:line 209
at Microsoft.Azure.Functions.PowerShellWorker.RequestProcessor.BindOutputFromResult(InvocationResponse response, AzFunctionInfo functionInfo, IDictionary results) in D:\a_work\1\s\src\RequestProcessor.cs:line 525
at Microsoft.Azure.Functions.PowerShellWorker.RequestProcessor.ProcessInvocationRequestImpl(StreamingMessage request, AzFunctionInfo functionInfo, PowerShellManager psManager, FunctionInvocationPerformanceStopwatch stopwatch) in D:\a_work\1\s\src\RequestProcessor.cs:line 342.
So instead, I have the following function code:
using namespace System.Net
param($ToolInvocationContext)
Write-Host "PowerShell MCP Tool trigger function processed a request."
$name = $ToolInvocationContext["arguments"]["name"] ?? "world"
Push-OutputBinding -Name "`$return" -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = "Hello, $name! This MCP tool executed successfully."
})
But this leads to an error that seems to come from the host (which reasonably expects an HTTP output binding to always have an HTTP trigger):
System.Private.CoreLib: Exception while executing function: Functions.HttpApproach. System.Private.CoreLib: Unable to cast object of type 'System.String' to type 'Microsoft.AspNetCore.Http.HttpRequest'.
Error processing request: '5' for client 'gaoy80sqog37p1o6'
Microsoft.Azure.Functions.Extensions.Mcp: Method not found. System.Private.CoreLib: Exception while executing function: Functions.HttpApproach. System.Private.CoreLib: Unable to cast object of type 'System.String' to type 'Microsoft.AspNetCore.Http.HttpRequest'.