Skip to content

Commit d7faa6b

Browse files
authored
Release v1.4.0 (#1622)
2 parents 4498146 + 3286450 commit d7faa6b

12 files changed

Lines changed: 528 additions & 17 deletions

File tree

.github/workflows/ci-code-coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
pattern: testresults-*
2525

2626
- name: Combine coverage reports
27-
uses: danielpalme/ReportGenerator-GitHub-Action@5.5.5
27+
uses: danielpalme/ReportGenerator-GitHub-Action@7ae927204961589fcb0b0be245c51fbbc87cbca2 # 5.5.5
2828
with:
2929
reports: "**/*.cobertura.xml"
3030
targetdir: "${{ github.workspace }}/report"

docs/concepts/tasks/tasks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ uid: tasks
1313
1414
The Model Context Protocol (MCP) supports [task-based execution] for long-running operations. Tasks enable a "call-now, fetch-later" pattern where clients can initiate operations that may take significant time to complete, then poll for status and retrieve results when ready.
1515

16-
[task-based execution]: https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks
16+
[task-based execution]: https://modelcontextprotocol.io/seps/1686-tasks
1717

1818
## Overview
1919

@@ -601,4 +601,4 @@ While this file-based approach demonstrates the pattern, production systems shou
601601
- <xref:ModelContextProtocol.InMemoryMcpTaskStore>
602602
- <xref:ModelContextProtocol.Protocol.McpTask>
603603
- <xref:ModelContextProtocol.Protocol.McpTaskStatus>
604-
- [MCP Tasks Specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)
604+
- [MCP Tasks Specification](https://modelcontextprotocol.io/seps/1686-tasks)

docs/concepts/transports/transports.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,63 @@ Key <xref:ModelContextProtocol.Client.StdioClientTransportOptions> properties:
3939
| `Command` | The executable to launch (required) |
4040
| `Arguments` | Command-line arguments for the process |
4141
| `WorkingDirectory` | Working directory for the server process |
42-
| `EnvironmentVariables` | Environment variables (merged with current; `null` values remove variables) |
42+
| `EnvironmentVariables` | Environment variables (merged with current when inheriting; `null` values remove variables) |
43+
| `InheritEnvironmentVariables` | Whether the server process inherits the current process's environment variables (default: `true`) |
4344
| `ShutdownTimeout` | Graceful shutdown timeout (default: 5 seconds) |
4445
| `StandardErrorLines` | Callback for stderr output from the server process |
4546
| `Name` | Optional transport identifier for logging |
4647

48+
#### Environment variable inheritance
49+
50+
By default, the server process inherits **all** environment variables from the current process. This includes credentials, tokens, proxy settings, and internal configuration that may be sensitive or irrelevant to the server. When running third-party or untrusted MCP servers, consider disabling inheritance to prevent unintentional credential leakage:
51+
52+
```csharp
53+
var transport = new StdioClientTransport(new StdioClientTransportOptions
54+
{
55+
Command = "my-mcp-server",
56+
InheritEnvironmentVariables = false,
57+
EnvironmentVariables = StdioClientTransportOptions.GetDefaultEnvironmentVariables(),
58+
});
59+
```
60+
61+
`GetDefaultEnvironmentVariables()` returns a curated set of environment variables (such as `PATH`, `HOME`, and standard system directories) that most child processes need to start correctly, without leaking credentials or other sensitive values from the parent process. The allowlist is aligned with the defaults used by the TypeScript and Python MCP SDKs. On Windows it also includes `PATHEXT`, which is required for the OS to recognize `.cmd` and `.bat` files as executable. You can add server-specific variables on top:
62+
63+
```csharp
64+
var env = StdioClientTransportOptions.GetDefaultEnvironmentVariables();
65+
env["MY_SERVER_API_KEY"] = apiKey;
66+
67+
var transport = new StdioClientTransport(new StdioClientTransportOptions
68+
{
69+
Command = "my-mcp-server",
70+
InheritEnvironmentVariables = false,
71+
EnvironmentVariables = env,
72+
});
73+
```
74+
75+
If you need to selectively forward a specific set of variables from the parent environment rather than using the curated allowlist, build the dictionary manually:
76+
77+
```csharp
78+
var env = new Dictionary<string, string?>();
79+
foreach (var name in new[] { "PATH", "HOME", "HTTP_PROXY", "HTTPS_PROXY" })
80+
{
81+
var value = Environment.GetEnvironmentVariable(name);
82+
if (value is not null)
83+
env[name] = value;
84+
}
85+
86+
var transport = new StdioClientTransport(new StdioClientTransportOptions
87+
{
88+
Command = "my-mcp-server",
89+
InheritEnvironmentVariables = false,
90+
EnvironmentVariables = env,
91+
});
92+
```
93+
94+
> [!WARNING]
95+
> **Security risk (inheriting):** Variables such as `AWS_SECRET_ACCESS_KEY`, `GITHUB_TOKEN`, `OPENAI_API_KEY`, and similar credentials present in the parent process automatically flow into the child process unless inheritance is disabled. This can unintentionally expose sensitive values to third-party or untrusted MCP servers.
96+
>
97+
> **Compatibility risk (not inheriting):** Disabling inheritance can cause the child process to fail to start or behave incorrectly if it relies on variables provided by the OS or shell. `GetDefaultEnvironmentVariables()` covers the most common requirements — `PATH`, `HOME`, and standard system directories — so for most servers it is a safe starting point. For servers that need additional variables not in the default set (such as `DOTNET_ROOT`, `LD_LIBRARY_PATH`, `JAVA_HOME`, or proxy settings like `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`), add them on top as shown in the example above.
98+
4799
#### stdio server
48100

49101
Use <xref:ModelContextProtocol.Server.StdioServerTransport> for servers that communicate over stdin/stdout:

docs/list-of-diagnostics.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ If you use experimental APIs, you will get one of the diagnostics shown below. T
2323

2424
| Diagnostic ID | Description |
2525
| :------------ | :---------- |
26-
| `MCPEXP001` | Experimental APIs for features in the MCP specification itself, including Tasks and Extensions. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). |
26+
| `MCPEXP001` | Experimental APIs for features in the MCP specification itself, including Tasks and Extensions. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/seps/1686-tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). |
2727
| `MCPEXP002` | Experimental SDK APIs unrelated to the MCP specification itself, including subclassing `McpClient`/`McpServer` (see [#1363](https://github.com/modelcontextprotocol/csharp-sdk/pull/1363)) and `RunSessionHandler`, which may be removed or change signatures in a future release (consider using `ConfigureSessionOptions` instead). |
2828

2929
## Obsolete APIs

samples/ChatWithTools/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
Command = "npx",
4040
Arguments = ["-y", "--verbose", "@modelcontextprotocol/server-everything"],
4141
Name = "Everything",
42+
InheritEnvironmentVariables = false,
43+
EnvironmentVariables = StdioClientTransportOptions.GetDefaultEnvironmentVariables(),
4244
}),
4345
clientOptions: new()
4446
{
@@ -82,4 +84,4 @@
8284
Console.WriteLine();
8385

8486
messages.AddMessages(updates);
85-
}
87+
}

samples/QuickstartClient/Program.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
Name = "Demo Server",
3232
Command = command,
3333
Arguments = arguments,
34+
InheritEnvironmentVariables = false,
35+
EnvironmentVariables = GetMinimalDotNetEnvironment(),
3436
});
3537
}
3638
await using var mcpClient = await McpClient.CreateAsync(clientTransport!);
@@ -122,3 +124,21 @@ static string GetCurrentSourceDirectory([CallerFilePath] string? currentFile = n
122124
Debug.Assert(!string.IsNullOrWhiteSpace(currentFile));
123125
return Path.GetDirectoryName(currentFile) ?? throw new InvalidOperationException("Unable to determine source directory.");
124126
}
127+
128+
// Returns the safe default environment variables plus extras needed by 'dotnet run'.
129+
// Omitting variables the server doesn't need prevents unintentional leakage of
130+
// credentials or other sensitive values present in the parent process.
131+
static Dictionary<string, string?> GetMinimalDotNetEnvironment()
132+
{
133+
var env = StdioClientTransportOptions.GetDefaultEnvironmentVariables();
134+
// 'dotnet run' also needs DOTNET_ROOT and NUGET_PACKAGES to find the .NET runtime and package cache.
135+
foreach (var key in (string[])["DOTNET_ROOT", "NUGET_PACKAGES"])
136+
{
137+
var value = Environment.GetEnvironmentVariable(key);
138+
if (value is not null)
139+
{
140+
env[key] = value;
141+
}
142+
}
143+
return env;
144+
}

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<PackageProjectUrl>https://csharp.sdk.modelcontextprotocol.io</PackageProjectUrl>
66
<RepositoryUrl>https://github.com/modelcontextprotocol/csharp-sdk</RepositoryUrl>
77
<RepositoryType>git</RepositoryType>
8-
<VersionPrefix>1.3.0</VersionPrefix>
8+
<VersionPrefix>1.4.0</VersionPrefix>
99
<Authors>ModelContextProtocol</Authors>
1010
<Copyright>© Model Context Protocol a Series of LF Projects, LLC.</Copyright>
1111
<PackageTags>ModelContextProtocol;mcp;ai;llm</PackageTags>

src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,20 @@ public async Task HandleDeleteRequestAsync(HttpContext context)
209209
}
210210

211211
var sessionId = context.Request.Headers[McpSessionIdHeaderName].ToString();
212-
if (sessionManager.TryRemove(sessionId, out var session))
212+
if (string.IsNullOrEmpty(sessionId) || !sessionManager.TryGetValue(sessionId, out var session))
213+
{
214+
return;
215+
}
216+
217+
if (!session.HasSameUserId(context.User))
218+
{
219+
await WriteJsonRpcErrorAsync(context,
220+
"Forbidden: The currently authenticated user does not match the user who initiated the session.",
221+
StatusCodes.Status403Forbidden);
222+
return;
223+
}
224+
225+
if (sessionManager.TryRemove(sessionId, out session))
213226
{
214227
await session.DisposeAsync();
215228
}

src/ModelContextProtocol.Core/Client/StdioClientTransport.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken =
111111
#endif
112112
}
113113

114+
if (!_options.InheritEnvironmentVariables)
115+
{
116+
startInfo.Environment.Clear();
117+
}
118+
114119
if (_options.EnvironmentVariables != null)
115120
{
116121
foreach (var entry in _options.EnvironmentVariables)
@@ -121,9 +126,8 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken =
121126

122127
if (logger.IsEnabled(LogLevel.Trace))
123128
{
124-
LogCreateProcessForTransportSensitive(logger, endpointName, _options.Command,
129+
LogCreateProcessForTransportDetailed(logger, endpointName, _options.Command,
125130
startInfo.Arguments,
126-
string.Join(", ", startInfo.Environment.Select(kvp => $"{kvp.Key}={kvp.Value}")),
127131
startInfo.WorkingDirectory);
128132
}
129133
else
@@ -295,8 +299,8 @@ private static string EscapeArgumentString(string argument) =>
295299
[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} starting server process. Command: '{Command}'.")]
296300
private static partial void LogCreateProcessForTransport(ILogger logger, string endpointName, string command);
297301

298-
[LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} starting server process. Command: '{Command}', Arguments: {Arguments}, Environment: {Environment}, Working directory: {WorkingDirectory}.")]
299-
private static partial void LogCreateProcessForTransportSensitive(ILogger logger, string endpointName, string command, string? arguments, string environment, string workingDirectory);
302+
[LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} starting server process. Command: '{Command}', Arguments: {Arguments}, Working directory: {WorkingDirectory}.")]
303+
private static partial void LogCreateProcessForTransportDetailed(ILogger logger, string endpointName, string command, string? arguments, string workingDirectory);
300304

301305
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} failed to start server process.")]
302306
private static partial void LogTransportProcessStartFailed(ILogger logger, string endpointName);

0 commit comments

Comments
 (0)