diff --git a/README.MD b/README.MD
index a147e2f..e25970b 100644
--- a/README.MD
+++ b/README.MD
@@ -21,6 +21,7 @@ This library aims to provide a clean, specification-compliant implementation of
- MCP client implementation for .NET applications
- Support for stdio and SSE transports
- Support for all MCP capabilities: Tool, Resource, Prompt, Sampling, Roots
+- Support for the Completion utility capability
- Support for pagination and notifications
- Async/await pattern throughout
- Comprehensive logging support
diff --git a/src/mcpdotnet/Client/IMcpClient.cs b/src/mcpdotnet/Client/IMcpClient.cs
index fb4e470..ed6e5d5 100644
--- a/src/mcpdotnet/Client/IMcpClient.cs
+++ b/src/mcpdotnet/Client/IMcpClient.cs
@@ -99,6 +99,16 @@ public interface IMcpClient : IAsyncDisposable
///
Task ReadResourceAsync(string uri, CancellationToken cancellationToken = default);
+ ///
+ /// Gets the completion options for a resource or prompt reference and (named) argument.
+ ///
+ /// A resource (uri) or prompt (name) reference
+ /// Name of argument. Must be non-null and non-empty.
+ /// Value of argument. Must be non-null.
+ /// A token to cancel the operation.
+ ///
+ Task GetCompletionAsync(Reference reference, string argumentName, string argumentValue, CancellationToken cancellationToken = default);
+
///
/// Subscribes to a resource on the server.
///
diff --git a/src/mcpdotnet/Client/McpClient.cs b/src/mcpdotnet/Client/McpClient.cs
index 0cd3abf..0339bd7 100644
--- a/src/mcpdotnet/Client/McpClient.cs
+++ b/src/mcpdotnet/Client/McpClient.cs
@@ -264,6 +264,39 @@ internal class McpClient : IMcpClient
).ConfigureAwait(false);
}
+ ///
+ public async Task GetCompletionAsync(Reference reference, string argumentName, string argumentValue, CancellationToken cancellationToken = default)
+ {
+ if (!reference.Validate(out string validationMessage))
+ {
+ _logger.InvalidCompletionReference(_serverConfig.Id, _serverConfig.Name, reference.ToString(), validationMessage);
+ throw new McpClientException($"Invalid reference: {validationMessage}");
+ }
+ if (string.IsNullOrWhiteSpace(argumentName))
+ {
+ _logger.InvalidCompletionArgumentName(_serverConfig.Id, _serverConfig.Name, argumentName);
+ throw new McpClientException("Argument name cannot be null or empty");
+ }
+ if (argumentValue is null)
+ {
+ _logger.InvalidCompletionArgumentValue(_serverConfig.Id, _serverConfig.Name, argumentValue);
+ throw new McpClientException("Argument value cannot be null");
+ }
+
+ _logger.GettingCompletion(_serverConfig.Id, _serverConfig.Name, reference.ToString(), argumentName, argumentValue);
+ return await SendRequestAsync(
+ new JsonRpcRequest
+ {
+ Method = "completion/complete",
+ Params = new Dictionary
+ {
+ ["ref"] = reference,
+ ["argument"] = new Argument { Name = argumentName, Value = argumentValue }
+ }
+ },
+ cancellationToken
+ ).ConfigureAwait(false);
+ }
///
public async Task SubscribeToResourceAsync(string uri, CancellationToken cancellationToken = default)
diff --git a/src/mcpdotnet/Logging/Log.cs b/src/mcpdotnet/Logging/Log.cs
index 9b0e01b..67251a7 100644
--- a/src/mcpdotnet/Logging/Log.cs
+++ b/src/mcpdotnet/Logging/Log.cs
@@ -322,4 +322,55 @@ internal static partial class Log
string serverName,
string data,
Exception exception);
+
+ // trace _logger.InvalidCompletionReference(_serverConfig.Id, _serverConfig.Name, reference, validationMessage);
+ [LoggerMessage(
+ EventId = 7004,
+ Level = LogLevel.Trace,
+ Message = "Invalid completion reference {reference} for server {serverId} ({serverName}): {validationMessage}"
+ )]
+ public static partial void InvalidCompletionReference(
+ this ILogger logger,
+ string serverId,
+ string serverName,
+ string reference,
+ string validationMessage);
+
+ // trace _logger.InvalidACompletionArgumentName(_serverConfig.Id, _serverConfig.Name, argumentName);
+ [LoggerMessage(
+ EventId = 7005,
+ Level = LogLevel.Trace,
+ Message = "Invalid completion argument name {argumentName} for server {serverId} ({serverName})"
+ )]
+ public static partial void InvalidCompletionArgumentName(
+ this ILogger logger,
+ string serverId,
+ string serverName,
+ string argumentName);
+
+ // trace _logger.InvalidCompletionArgumentValue(_serverConfig.Id, _serverConfig.Name, argumentValue);
+ [LoggerMessage(
+ EventId = 7006,
+ Level = LogLevel.Trace,
+ Message = "Invalid completion argument value {argumentValue} for server {serverId} ({serverName})"
+ )]
+ public static partial void InvalidCompletionArgumentValue(
+ this ILogger logger,
+ string serverId,
+ string serverName,
+ string argumentValue);
+
+ // debug _logger.GettingCompletion(_serverConfig.Id, _serverConfig.Name, reference, argumentName, argumentValue);
+ [LoggerMessage(
+ EventId = 7007,
+ Level = LogLevel.Debug,
+ Message = "Getting completion for server {serverId} ({serverName}) with reference {reference}, argument name {argumentName}, argument value {argumentValue}"
+ )]
+ public static partial void GettingCompletion(
+ this ILogger logger,
+ string serverId,
+ string serverName,
+ string reference,
+ string argumentName,
+ string argumentValue);
}
\ No newline at end of file
diff --git a/src/mcpdotnet/Protocol/Types/Argument.cs b/src/mcpdotnet/Protocol/Types/Argument.cs
new file mode 100644
index 0000000..8aa6397
--- /dev/null
+++ b/src/mcpdotnet/Protocol/Types/Argument.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace McpDotNet.Protocol.Types;
+
+///
+/// Used for completion requests to provide additional context for the completion options.
+///
+public class Argument
+{
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = string.Empty;
+
+ [JsonPropertyName("value")]
+ public string Value { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/src/mcpdotnet/Protocol/Types/Completion.cs b/src/mcpdotnet/Protocol/Types/Completion.cs
new file mode 100644
index 0000000..0f53706
--- /dev/null
+++ b/src/mcpdotnet/Protocol/Types/Completion.cs
@@ -0,0 +1,27 @@
+using System.Text.Json.Serialization;
+
+namespace McpDotNet.Protocol.Types;
+
+///
+/// Represents a completion object in the server's response.
+///
+public class Completion
+{
+ ///
+ /// An array of completion values. Must not exceed 100 items.
+ ///
+ [JsonPropertyName("values")]
+ public string[] Values { get; set; } = new string[0];
+
+ ///
+ /// The total number of completion options available. This can exceed the number of values actually sent in the response.
+ ///
+ [JsonPropertyName("total")]
+ public int? Total { get; set; }
+
+ ///
+ /// Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
+ ///
+ [JsonPropertyName("hasMore")]
+ public bool? HasMore { get; set; }
+}
diff --git a/src/mcpdotnet/Protocol/Types/Reference.cs b/src/mcpdotnet/Protocol/Types/Reference.cs
new file mode 100644
index 0000000..bf259c7
--- /dev/null
+++ b/src/mcpdotnet/Protocol/Types/Reference.cs
@@ -0,0 +1,69 @@
+using System.Text.Json.Serialization;
+
+namespace McpDotNet.Protocol.Types;
+
+///
+/// Represents a reference to a resource or prompt. Umbrella type for both ResourceReference and PromptReference from the spec schema.
+///
+public class Reference
+{
+ ///
+ /// The type of content. Can be ref/resource or ref/prompt.
+ ///
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = string.Empty;
+
+ [JsonPropertyName("uri")]
+ public string? Uri { get; set; }
+
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ ///
+ /// Returns a string representation of the reference.
+ ///
+ public override string ToString()
+ {
+ return $"\"{Type}\": \"{Uri ?? Name}\"";
+ }
+
+ ///
+ /// Validates the reference object.
+ ///
+ public bool Validate(out string validationMessage)
+ {
+ if (Type == "ref/resource")
+ {
+ if (string.IsNullOrEmpty(Uri))
+ {
+ validationMessage = "Uri is required for ref/resource";
+ return false;
+ }
+ }
+ else if (Type == "ref/prompt")
+ {
+ if (string.IsNullOrEmpty(Name))
+ {
+ validationMessage = "Name is required for ref/prompt";
+ return false;
+ }
+ }
+ else
+ {
+ validationMessage = $"Unknown reference type: {Type}";
+ return false;
+ }
+
+ validationMessage = string.Empty;
+ return true;
+ }
+}
+
+///
+/// The server's response to a completion/complete request
+///
+public class CompleteResult
+{
+ [JsonPropertyName("completion")]
+ public Completion Completion { get; set; } = new Completion();
+}
diff --git a/src/mcpdotnet/mcpdotnet.csproj b/src/mcpdotnet/mcpdotnet.csproj
index aa119d9..7d5f86a 100644
--- a/src/mcpdotnet/mcpdotnet.csproj
+++ b/src/mcpdotnet/mcpdotnet.csproj
@@ -9,7 +9,7 @@
mcpdotnet
- 0.7.0.1
+ 0.8.0.1
PederHP
.NET client library for the Model Context Protocol (MCP)
https://github.com/PederHP/mcpdotnet
diff --git a/tests/mcpdotnet.Tests/IntegrationTestFixture.cs b/tests/mcpdotnet.Tests/IntegrationTestFixture.cs
index 2805d9f..9d95cc6 100644
--- a/tests/mcpdotnet.Tests/IntegrationTestFixture.cs
+++ b/tests/mcpdotnet.Tests/IntegrationTestFixture.cs
@@ -30,6 +30,7 @@ public class IntegrationTestFixture : IDisposable
TransportOptions = new Dictionary
{
["command"] = "npx",
+ // Change to ["arguments"] = "mcp-server-everything" if you want to run the server locally after creating a symlink
["arguments"] = "-y @modelcontextprotocol/server-everything",
}
};
diff --git a/tests/mcpdotnet.Tests/IntegrationTests.cs b/tests/mcpdotnet.Tests/IntegrationTests.cs
index 854be6e..a3f651b 100644
--- a/tests/mcpdotnet.Tests/IntegrationTests.cs
+++ b/tests/mcpdotnet.Tests/IntegrationTests.cs
@@ -180,6 +180,68 @@ public class IntegrationTests : IClassFixture
Assert.NotNull(result.Contents[0].Blob);
}
+ ///
+ /// Note that as of 19th January 2025, the everything server published to npx does not support prompt completion.
+ /// However, the prompt completion is implemented in the github.com/modelcontextprotocol/servers repository.
+ /// You can clone this repo, and build locally then change the config to use a local symlink instead of npx until this is fixed, if you wish to run this test.
+ /// TransportOptions = new Dictionary
+ /// {
+ /// ["command"] = "npx",
+ /// ["arguments"] = "mcp-server-everything" // changed from "-y @modelcontextprotocol/server-everything"
+ /// }
+ ///
+ [Fact]
+ public async Task GetCompletion_Stdio_ResourceReference()
+ {
+ // arrange
+
+ // act
+ var client = await _fixture.Factory.GetClientAsync("everything");
+ var result = await client.GetCompletionAsync(new Reference
+ {
+ Type = "ref/resource",
+ Uri = "test://static/resource/1"
+ },
+ "argument_name", "1",
+ CancellationToken.None
+ );
+
+ Assert.NotNull(result);
+ Assert.Single(result.Completion.Values);
+ Assert.True(result.Completion.Values[0] == "1");
+ }
+
+ ///
+ /// Note that as of 19th January 2025, the everything server published to npx does not support prompt completion.
+ /// However, the prompt completion is implemented in the github.com/modelcontextprotocol/servers repository.
+ /// You can clone this repo, and build locally then change the config to use a local symlink instead of npx until this is fixed, if you wish to run this test.
+ /// TransportOptions = new Dictionary
+ /// {
+ /// ["command"] = "npx",
+ /// ["arguments"] = "mcp-server-everything" // changed from "-y @modelcontextprotocol/server-everything"
+ /// }
+ ///
+ [Fact]
+ public async Task GetCompletion_Stdio_PromptReference()
+ {
+ // arrange
+
+ // act
+ var client = await _fixture.Factory.GetClientAsync("everything");
+ var result = await client.GetCompletionAsync(new Reference
+ {
+ Type = "ref/prompt",
+ Name = "irrelevant"
+ },
+ argumentName:"style", argumentValue:"fo",
+ CancellationToken.None
+ );
+
+ Assert.NotNull(result);
+ Assert.Single(result.Completion.Values);
+ Assert.True(result.Completion.Values[0] == "formal");
+ }
+
[Fact]
public async Task Sampling_Stdio_EverythingServer()
{