Skip to content

Commit f509d38

Browse files
authored
Merge pull request #281 from Resgrid/develop
RE1-T102 MCP server over HTTP fix
2 parents 1da63ab + 88c2bd5 commit f509d38

24 files changed

Lines changed: 849 additions & 39 deletions

Core/Resgrid.Config/McpConfig.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Resgrid.Config
1+
namespace Resgrid.Config
22
{
33
/// <summary>
44
/// Configuration settings for the Model Context Protocol (MCP) server
@@ -17,9 +17,29 @@ public static class McpConfig
1717
public static string ServerVersion = "1.0.0";
1818

1919
/// <summary>
20-
/// The transport mechanism for MCP (e.g., "stdio")
20+
/// The transport mechanism for MCP (e.g., "stdio", "http")
2121
/// </summary>
22-
public static string Transport = "stdio";
22+
public static string Transport = "http";
23+
24+
/// <summary>
25+
/// Enable CORS for HTTP transport (allows cross-origin requests)
26+
/// </summary>
27+
public static bool EnableCors = true;
28+
29+
/// <summary>
30+
/// Allowed CORS origins (comma-separated list). Empty or "*" allows all origins.
31+
/// </summary>
32+
public static string CorsAllowedOrigins = "*";
33+
34+
/// <summary>
35+
/// Enable HTTP transport endpoint
36+
/// </summary>
37+
public static bool EnableHttpTransport = true;
38+
39+
/// <summary>
40+
/// Enable stdio transport (for backwards compatibility)
41+
/// </summary>
42+
public static bool EnableStdioTransport = false;
2343
}
2444
}
2545

Tests/Resgrid.Tests/Resgrid.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,12 @@
5050
<ProjectReference Include="..\..\Providers\Resgrid.Providers.Number\Resgrid.Providers.Number.csproj" />
5151
<ProjectReference Include="..\..\Providers\Resgrid.Providers.Pdf\Resgrid.Providers.Pdf.csproj" />
5252
<ProjectReference Include="..\..\Repositories\Resgrid.Repositories.DataRepository\Resgrid.Repositories.DataRepository.csproj" />
53+
<ProjectReference Include="..\..\Web\Resgrid.Web.Mcp\Resgrid.Web.Mcp.csproj" />
5354
<ProjectReference Include="..\..\Workers\Resgrid.Workers.Framework\Resgrid.Workers.Framework.csproj" />
5455
</ItemGroup>
5556
<ItemGroup>
5657
<None Update="Data\TestCall.json">
5758
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
5859
</None>
5960
</ItemGroup>
60-
</Project>
61+
</Project>
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
using NUnit.Framework;
2+
using Resgrid.Web.Mcp.Infrastructure;
3+
4+
namespace Resgrid.Tests.Web.Mcp
5+
{
6+
/// <summary>
7+
/// Unit tests for SensitiveDataRedactor to verify sensitive field redaction
8+
/// </summary>
9+
[TestFixture]
10+
public sealed class SensitiveDataRedactorTests
11+
{
12+
private const string RedactedValue = "***REDACTED***";
13+
14+
[Test]
15+
public void RedactSensitiveFields_ShouldRedactPassword()
16+
{
17+
// Arrange
18+
var jsonWithPassword = @"{""username"":""john.doe"",""password"":""secret123""}";
19+
20+
// Act
21+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(jsonWithPassword);
22+
23+
// Assert
24+
Assert.That(redacted, Does.Contain(RedactedValue), "Redacted output should contain redaction marker");
25+
Assert.That(redacted, Does.Not.Contain("secret123"), "Redacted output should not contain original password");
26+
Assert.That(redacted, Does.Not.Contain("john.doe"), "Redacted output should not contain username value");
27+
}
28+
29+
[Test]
30+
public void RedactSensitiveFields_ShouldRedactTokenAndApiKey()
31+
{
32+
// Arrange
33+
var jsonWithToken = @"{""token"":""Bearer abc123"",""apikey"":""xyz789"",""data"":""safe data""}";
34+
35+
// Act
36+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(jsonWithToken);
37+
38+
// Assert
39+
Assert.That(redacted, Does.Contain(RedactedValue), "Redacted output should contain redaction marker");
40+
Assert.That(redacted, Does.Not.Contain("Bearer abc123"), "Redacted output should not contain original token");
41+
Assert.That(redacted, Does.Not.Contain("xyz789"), "Redacted output should not contain original API key");
42+
Assert.That(redacted, Does.Contain("safe data"), "Redacted output should preserve non-sensitive data");
43+
}
44+
45+
[Test]
46+
public void RedactSensitiveFields_ShouldRedactNestedSensitiveFields()
47+
{
48+
// Arrange
49+
var nestedJson = @"{""user"":{""username"":""jane"",""password"":""pass456""},""sessionToken"":""token123""}";
50+
51+
// Act
52+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(nestedJson);
53+
54+
// Assert
55+
Assert.That(redacted, Does.Contain(RedactedValue), "Redacted output should contain redaction marker");
56+
Assert.That(redacted, Does.Not.Contain("jane"), "Redacted output should not contain nested username");
57+
Assert.That(redacted, Does.Not.Contain("pass456"), "Redacted output should not contain nested password");
58+
Assert.That(redacted, Does.Not.Contain("token123"), "Redacted output should not contain session token");
59+
}
60+
61+
[Test]
62+
public void RedactSensitiveFields_ShouldRedactJsonRpcRequest()
63+
{
64+
// Arrange
65+
var jsonRpcRequest = @"{""jsonrpc"":""2.0"",""method"":""authenticate"",""params"":{""username"":""admin"",""password"":""admin123""},""id"":1}";
66+
67+
// Act
68+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(jsonRpcRequest);
69+
70+
// Assert
71+
Assert.That(redacted, Does.Contain(RedactedValue), "Redacted output should contain redaction marker");
72+
Assert.That(redacted, Does.Not.Contain("admin123"), "Redacted output should not contain password from params");
73+
Assert.That(redacted, Does.Not.Contain("admin"), "Redacted output should not contain username from params");
74+
Assert.That(redacted, Does.Contain("authenticate"), "Redacted output should preserve method name");
75+
Assert.That(redacted, Does.Contain("2.0"), "Redacted output should preserve jsonrpc version");
76+
}
77+
78+
[Test]
79+
public void RedactSensitiveFields_ShouldHandleInvalidJson()
80+
{
81+
// Arrange
82+
var invalidJson = "not valid json {";
83+
84+
// Act
85+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(invalidJson);
86+
87+
// Assert
88+
Assert.That(redacted, Is.Not.Null, "Should return non-null result for invalid JSON");
89+
Assert.That(redacted, Is.Not.Empty, "Should return non-empty result for invalid JSON");
90+
Assert.That(redacted, Does.Not.Contain("not valid json"), "Should not contain original invalid JSON content");
91+
}
92+
93+
[Test]
94+
public void RedactSensitiveFields_ShouldHandleEmptyString()
95+
{
96+
// Arrange
97+
var emptyJson = string.Empty;
98+
99+
// Act
100+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(emptyJson);
101+
102+
// Assert
103+
Assert.That(redacted, Is.Empty, "Should return empty string for empty input");
104+
}
105+
106+
[Test]
107+
public void RedactSensitiveFields_ShouldHandleNullString()
108+
{
109+
// Arrange
110+
string nullJson = null;
111+
112+
// Act
113+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(nullJson);
114+
115+
// Assert
116+
Assert.That(redacted, Is.Empty, "Should return empty string for null input");
117+
}
118+
119+
[Test]
120+
public void RedactSensitiveFields_ShouldPreserveNonSensitiveFields()
121+
{
122+
// Arrange
123+
var jsonWithMixedFields = @"{""id"":42,""name"":""John"",""email"":""john@example.com"",""status"":""active""}";
124+
125+
// Act
126+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(jsonWithMixedFields);
127+
128+
// Assert
129+
Assert.That(redacted, Does.Contain(RedactedValue), "Should redact email field");
130+
Assert.That(redacted, Does.Not.Contain("john@example.com"), "Should not contain original email");
131+
Assert.That(redacted, Does.Contain("42"), "Should preserve id field");
132+
Assert.That(redacted, Does.Contain("John"), "Should preserve name field");
133+
Assert.That(redacted, Does.Contain("active"), "Should preserve status field");
134+
}
135+
136+
[Test]
137+
public void RedactSensitiveFields_ShouldRedactArraysWithSensitiveData()
138+
{
139+
// Arrange
140+
var jsonWithArray = @"{""users"":[{""username"":""user1"",""password"":""pass1""},{""username"":""user2"",""password"":""pass2""}]}";
141+
142+
// Act
143+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(jsonWithArray);
144+
145+
// Assert
146+
Assert.That(redacted, Does.Contain(RedactedValue), "Should redact sensitive fields in array");
147+
Assert.That(redacted, Does.Not.Contain("user1"), "Should not contain first username");
148+
Assert.That(redacted, Does.Not.Contain("user2"), "Should not contain second username");
149+
Assert.That(redacted, Does.Not.Contain("pass1"), "Should not contain first password");
150+
Assert.That(redacted, Does.Not.Contain("pass2"), "Should not contain second password");
151+
}
152+
153+
[Test]
154+
public void RedactSensitiveFields_ShouldRedactCommonSensitiveFieldNames()
155+
{
156+
// Arrange
157+
var jsonWithVariousSensitiveFields = @"{
158+
""password"":""secret"",
159+
""token"":""token123"",
160+
""ssn"":""123-45-6789"",
161+
""apikey"":""key123"",
162+
""api_key"":""key456"",
163+
""secret"":""secret789"",
164+
""authorization"":""Bearer xyz"",
165+
""auth"":""auth123"",
166+
""credentials"":""creds"",
167+
""credit_card"":""4111111111111111"",
168+
""creditcard"":""4111111111111111"",
169+
""cvv"":""123"",
170+
""pin"":""1234""
171+
}";
172+
173+
// Act
174+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(jsonWithVariousSensitiveFields);
175+
176+
// Assert
177+
Assert.That(redacted, Does.Contain(RedactedValue), "Should contain redaction marker");
178+
Assert.That(redacted, Does.Not.Contain("secret"), "Should not contain 'secret' value");
179+
Assert.That(redacted, Does.Not.Contain("token123"), "Should not contain token value");
180+
Assert.That(redacted, Does.Not.Contain("123-45-6789"), "Should not contain SSN");
181+
Assert.That(redacted, Does.Not.Contain("key123"), "Should not contain apikey value");
182+
Assert.That(redacted, Does.Not.Contain("key456"), "Should not contain api_key value");
183+
Assert.That(redacted, Does.Not.Contain("secret789"), "Should not contain secret value");
184+
Assert.That(redacted, Does.Not.Contain("Bearer xyz"), "Should not contain authorization value");
185+
Assert.That(redacted, Does.Not.Contain("auth123"), "Should not contain auth value");
186+
Assert.That(redacted, Does.Not.Contain("creds"), "Should not contain credentials value");
187+
Assert.That(redacted, Does.Not.Contain("4111111111111111"), "Should not contain credit card number");
188+
Assert.That(redacted, Does.Not.Contain("1234"), "Should not contain PIN");
189+
}
190+
191+
[Test]
192+
public void RedactSensitiveFields_ShouldHandleCaseInsensitiveFieldNames()
193+
{
194+
// Arrange
195+
var jsonWithMixedCase = @"{""Password"":""secret"",""TOKEN"":""token123"",""ApiKey"":""key456""}";
196+
197+
// Act
198+
var redacted = SensitiveDataRedactor.RedactSensitiveFields(jsonWithMixedCase);
199+
200+
// Assert
201+
Assert.That(redacted, Does.Contain(RedactedValue), "Should redact case-insensitive field names");
202+
Assert.That(redacted, Does.Not.Contain("secret"), "Should not contain password value");
203+
Assert.That(redacted, Does.Not.Contain("token123"), "Should not contain token value");
204+
Assert.That(redacted, Does.Not.Contain("key456"), "Should not contain API key value");
205+
}
206+
}
207+
}
208+

Web/Resgrid.Web.Mcp/Controllers/HealthController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public HealthController(
3636
/// </summary>
3737
/// <returns>HealthResult object with the server health status</returns>
3838
[HttpGet("current")]
39+
[HttpGet("getcurrent")]
3940
public async Task<IActionResult> GetCurrent()
4041
{
4142
var result = new HealthResult

0 commit comments

Comments
 (0)