Skip to content

Commit 475fd03

Browse files
committed
Add timeout_seconds parameter to invoke_expression
1 parent 69eb939 commit 475fd03

6 files changed

Lines changed: 34 additions & 23 deletions

File tree

PowerShell.MCP.Proxy/Models/JsonRpcModels.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ public class InvokeExpressionParams : PowerShellMcpParams
2525

2626
[JsonPropertyName("pipeline")]
2727
public string Pipeline { get; set; } = string.Empty;
28+
29+
[JsonPropertyName("timeout_seconds")]
30+
public int TimeoutSeconds { get; set; } = 170;
2831
}
2932

3033

34+
3135
public class StartPowerShellConsoleParams : PowerShellMcpParams
3236
{
3337
[JsonPropertyName("name")]

PowerShell.MCP.Proxy/Services/IPowerShellService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public interface IPowerShellService
99
Task<GetStatusResponse?> GetStatusAsync(CancellationToken cancellationToken = default);
1010
Task<GetStatusResponse?> GetStatusFromPipeAsync(string pipeName, CancellationToken cancellationToken = default);
1111
Task<string> ConsumeOutputFromPipeAsync(string pipeName, CancellationToken cancellationToken = default);
12-
Task<string> InvokeExpressionAsync(string command, CancellationToken cancellationToken = default);
13-
Task<string> InvokeExpressionToPipeAsync(string pipeName, string command, CancellationToken cancellationToken = default);
12+
Task<string> InvokeExpressionAsync(string command, int timeoutSeconds = 170, CancellationToken cancellationToken = default);
13+
Task<string> InvokeExpressionToPipeAsync(string pipeName, string command, int timeoutSeconds = 170, CancellationToken cancellationToken = default);
1414
Task<string> StartNewConsoleAsync(CancellationToken cancellationToken = default);
1515
}

PowerShell.MCP.Proxy/Services/PowerShellService.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,12 @@ public async Task<string> ConsumeOutputFromPipeAsync(string pipeName, Cancellati
9595
return await _namedPipeClient.SendRequestToAsync(pipeName, jsonRequest);
9696
}
9797

98-
public async Task<string> InvokeExpressionAsync(string pipeline, CancellationToken cancellationToken = default)
98+
public async Task<string> InvokeExpressionAsync(string pipeline, int timeoutSeconds = 170, CancellationToken cancellationToken = default)
9999
{
100100
var requestParams = new InvokeExpressionParams
101101
{
102-
Pipeline = pipeline
102+
Pipeline = pipeline,
103+
TimeoutSeconds = timeoutSeconds
103104
};
104105

105106
var jsonRequest = JsonSerializer.Serialize(requestParams, PowerShellJsonRpcContext.Default.InvokeExpressionParams);
@@ -114,11 +115,12 @@ public async Task<string> InvokeExpressionAsync(string pipeline, CancellationTok
114115
return response;
115116
}
116117

117-
public async Task<string> InvokeExpressionToPipeAsync(string pipeName, string pipeline, CancellationToken cancellationToken = default)
118+
public async Task<string> InvokeExpressionToPipeAsync(string pipeName, string pipeline, int timeoutSeconds = 170, CancellationToken cancellationToken = default)
118119
{
119120
var requestParams = new InvokeExpressionParams
120121
{
121-
Pipeline = pipeline
122+
Pipeline = pipeline,
123+
TimeoutSeconds = timeoutSeconds
122124
};
123125

124126
var jsonRequest = JsonSerializer.Serialize(requestParams, PowerShellJsonRpcContext.Default.InvokeExpressionParams);

PowerShell.MCP.Proxy/Tools/PowerShellTools.cs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ namespace PowerShell.MCP.Proxy.Tools;
1010
[McpServerToolType]
1111
public class PowerShellTools
1212
{
13-
// Error message constant definitions
14-
private const string ERROR_CONSOLE_NOT_RUNNING = "The PowerShell 7 console is not running.";
1513
/// <summary>
1614
/// Finds a ready pipe. Returns (pipeName, consoleSwitched).
1715
/// </summary>
@@ -170,15 +168,6 @@ private static string TruncatePipeline(string pipeline, int maxLength = 30)
170168
return normalized[..(maxLength - 3)] + "...";
171169
}
172170

173-
private static string BuildNotExecutedResponse(string message, string jsonContent)
174-
{
175-
var response = new StringBuilder();
176-
response.AppendLine(message);
177-
response.AppendLine();
178-
response.Append(jsonContent);
179-
return response.ToString();
180-
}
181-
182171
[McpServerTool]
183172
[Description("Retrieves the current location and all available drives (providers) from the PowerShell session. Returns current_location and other_drive_locations array. Call this when you need to understand the current PowerShell context, as users may change location during the session. When executing multiple invoke_expression commands in succession, calling once at the beginning is sufficient.")]
184173
public static async Task<string> GetCurrentLocation(
@@ -281,8 +270,13 @@ public static async Task<string> InvokeExpression(
281270
IPowerShellService powerShellService,
282271
[Description("The PowerShell command or pipeline to execute. Both single-line and multi-line commands are supported, including if statements, loops, functions, and try-catch blocks.")]
283272
string pipeline,
273+
[Description("Timeout in seconds (0-170, default: 170). On timeout, execution continues in background and result is cached for retrieval on next MCP tool call. You can work on other tasks in parallel. Use 0 for commands requiring user interaction (e.g., pause, Read-Host).")]
274+
int timeout_seconds = 170,
284275
CancellationToken cancellationToken = default)
285276
{
277+
// Clamp timeout to valid range
278+
timeout_seconds = Math.Clamp(timeout_seconds, 0, 170);
279+
286280
// Find a ready pipe
287281
var (readyPipeName, consoleSwitched) = await FindReadyPipeAsync(powerShellService, cancellationToken);
288282

@@ -352,7 +346,7 @@ public static async Task<string> InvokeExpression(
352346
// Execute the command
353347
try
354348
{
355-
var result = await powerShellService.InvokeExpressionToPipeAsync(readyPipeName, pipeline, cancellationToken);
349+
var result = await powerShellService.InvokeExpressionToPipeAsync(readyPipeName, pipeline, timeout_seconds, cancellationToken);
356350

357351
// Check if pipeline is still running (timeout case)
358352
const string stillRunningMessage = "⧗ Pipeline is still running";

PowerShell.MCP/Services/NamedPipeServer.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,10 +533,18 @@ private static string ExecuteTool(string method, JsonElement parameters)
533533
private static (bool isTimeout, bool shouldCache) ExecuteInvokeExpression(JsonElement parameters)
534534
{
535535
var pipeline = parameters.GetProperty("pipeline").GetString() ?? "";
536-
return McpServerHost.ExecuteCommand(pipeline);
536+
var timeoutSeconds = parameters.TryGetProperty("timeout_seconds", out var timeoutElement)
537+
? timeoutElement.GetInt32()
538+
: 170;
539+
540+
// Clamp to valid range
541+
timeoutSeconds = Math.Clamp(timeoutSeconds, 0, 170);
542+
543+
return McpServerHost.ExecuteCommand(pipeline, timeoutSeconds);
537544
}
538545

539546

547+
540548
/// <summary>
541549
/// Receives a message from Named Pipe
542550
/// </summary>

PowerShell.MCP/Services/PowerShellCommunication.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ public static void NotifyResultReady(string result)
2626
/// <summary>
2727
/// Waits for result (blocking method)
2828
/// </summary>
29+
/// <param name="timeoutSeconds">Timeout in seconds (1-170)</param>
2930
/// <returns>Tuple of (isTimeout, shouldCache)</returns>
30-
public static (bool isTimeout, bool shouldCache) WaitForResult()
31+
public static (bool isTimeout, bool shouldCache) WaitForResult(int timeoutSeconds = 170)
3132
{
3233
_resultShouldCache = false;
3334
_resultReadyEvent.Reset();
3435

35-
bool signaled = _resultReadyEvent.WaitOne(170 * 1000);
36+
bool signaled = _resultReadyEvent.WaitOne(timeoutSeconds * 1000);
3637

3738
if (signaled)
3839
{
@@ -57,12 +58,14 @@ public static class McpServerHost
5758
/// <summary>
5859
/// Command execution (state management is handled by NamedPipeServer)
5960
/// </summary>
60-
public static (bool isTimeout, bool shouldCache) ExecuteCommand(string command)
61+
/// <param name="command">PowerShell command to execute</param>
62+
/// <param name="timeoutSeconds">Timeout in seconds (1-170)</param>
63+
public static (bool isTimeout, bool shouldCache) ExecuteCommand(string command, int timeoutSeconds = 170)
6164
{
6265
try
6366
{
6467
executeCommand = command;
65-
return PowerShellCommunication.WaitForResult();
68+
return PowerShellCommunication.WaitForResult(timeoutSeconds);
6669
}
6770
catch (Exception)
6871
{

0 commit comments

Comments
 (0)