parent
85cefc23ff
commit
c9ace236a0
28 changed files with 864 additions and 274 deletions
11
README.MD
11
README.MD
|
|
@ -16,7 +16,7 @@ For more information about MCP:
|
|||
This project is in early development. While functional, it may undergo significant changes as the protocol and implementation evolve.
|
||||
|
||||
### Current Limitations
|
||||
- Only supports the Tool capability (Resources, Prompts, Sampling, and Roots not yet implemented)
|
||||
- Only supports the Tools, Resources, and Prompts capabilities (Sampling and Roots not yet implemented)
|
||||
- Only stdio transport is currently supported (HTTP/SSE transport coming soon)
|
||||
- Client library only - server implementation support may be added in the future
|
||||
- Provider-agnostic implementation focused purely on the MCP protocol
|
||||
|
|
@ -28,7 +28,8 @@ The library's current focus is on implementing the core MCP protocol, with the m
|
|||
|
||||
- MCP client implementation for .NET applications
|
||||
- Support for stdio transport
|
||||
- Tool discovery and execution
|
||||
- Support for Tool, Resource, and Prompt capabilities
|
||||
- Support for pagination and notifications
|
||||
- Async/await pattern throughout
|
||||
- Compatible with .NET 8.0 and later
|
||||
|
||||
|
|
@ -62,7 +63,7 @@ You can then retrieve an IMcpClient reference using the `id` of a server from th
|
|||
```csharp
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
// Get the list of tools, for passing to an LLM
|
||||
var tools = await client.ListToolsAsync(CancellationToken.None);
|
||||
var tools = await client.ListToolsAsync();
|
||||
// Execute a tool, in practice this would normally be driven by LLM tool invocations
|
||||
var result = await client.CallToolAsync(
|
||||
"echo",
|
||||
|
|
@ -76,7 +77,9 @@ var result = await client.CallToolAsync(
|
|||
Console.WriteLine(result.Content.FirstOrDefault(c => c.Type == "text").Text);
|
||||
```
|
||||
|
||||
Note that you should pass CancellationToken objects suitable for your use case, to enable proper error handling, timeouts, etc.
|
||||
Note that you should pass CancellationToken objects suitable for your use case, to enable proper error handling, timeouts, etc. This example also does not paginate the tools list, which may be necessary for large tool sets. See the IntegrationTests project for an example of pagination, as well as examples of how to handle Prompts and Resources.
|
||||
|
||||
Additional examples and documentation will be added as in the near future.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
using McpDotNet.Configuration;
|
||||
using McpDotNet.Protocol.Messages;
|
||||
using McpDotNet.Protocol.Transport;
|
||||
using McpDotNet.Protocol.Types;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -73,7 +75,7 @@ public class IntegrationTests
|
|||
|
||||
// act
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
var tools = await client.ListToolsAsync(CancellationToken.None);
|
||||
var tools = await client.ListToolsAsync();
|
||||
|
||||
// assert
|
||||
Assert.NotNull(tools);
|
||||
|
|
@ -119,4 +121,224 @@ public class IntegrationTests
|
|||
var textContent = Assert.Single(result.Content.Where(c => c.Type == "text"));
|
||||
Assert.Equal("Echo: Hello MCP!", textContent.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListPrompts_Stdio_EverythingServer()
|
||||
{
|
||||
// arrange
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything"
|
||||
}
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
new[] { config },
|
||||
_defaultOptions
|
||||
);
|
||||
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
var prompts = await client.ListPromptsAsync();
|
||||
|
||||
// assert
|
||||
Assert.NotNull(prompts);
|
||||
Assert.NotEmpty(prompts.Prompts);
|
||||
// We could add specific assertions for the known prompts
|
||||
Assert.Contains(prompts.Prompts, p => p.Name == "simple_prompt");
|
||||
Assert.Contains(prompts.Prompts, p => p.Name == "complex_prompt");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPrompt_Stdio_SimplePrompt()
|
||||
{
|
||||
// arrange
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything"
|
||||
}
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
new[] { config },
|
||||
_defaultOptions
|
||||
);
|
||||
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
var result = await client.GetPromptAsync("simple_prompt", null, CancellationToken.None);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPrompt_Stdio_ComplexPrompt()
|
||||
{
|
||||
// arrange
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything"
|
||||
}
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
new[] { config },
|
||||
_defaultOptions
|
||||
);
|
||||
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "temperature", "0.7" },
|
||||
{ "style", "formal" }
|
||||
};
|
||||
var result = await client.GetPromptAsync("complex_prompt", arguments, CancellationToken.None);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPrompt_NonExistent_ThrowsException()
|
||||
{
|
||||
// arrange
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything"
|
||||
}
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
new[] { config },
|
||||
_defaultOptions
|
||||
);
|
||||
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
await Assert.ThrowsAsync<McpClientException>(() =>
|
||||
client.GetPromptAsync("non_existent_prompt", null, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListResources_Stdio_EverythingServer()
|
||||
{
|
||||
// arrange
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything"
|
||||
}
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
new[] { config },
|
||||
_defaultOptions
|
||||
);
|
||||
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
|
||||
List<Resource> allResources = new();
|
||||
string? cursor = null;
|
||||
do
|
||||
{
|
||||
var resources = await client.ListResourcesAsync(cursor, CancellationToken.None);
|
||||
allResources.AddRange(resources.Resources);
|
||||
cursor = resources.NextCursor;
|
||||
}
|
||||
while (cursor != null);
|
||||
|
||||
// The everything server provides 100 test resources
|
||||
Assert.Equal(100, allResources.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadResource_Stdio_TextResource()
|
||||
{
|
||||
// arrange
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything"
|
||||
}
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
new[] { config },
|
||||
_defaultOptions
|
||||
);
|
||||
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
// Odd numbered resources are text in the everything server (despite the docs saying otherwise)
|
||||
// 1 is index 0, which is "even" in the 0-based index
|
||||
var result = await client.ReadResourceAsync("test://static/resource/1", CancellationToken.None);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result.Contents);
|
||||
Assert.NotNull(result.Contents[0].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadResource_Stdio_BinaryResource()
|
||||
{
|
||||
// arrange
|
||||
var config = new McpServerConfig
|
||||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
["arguments"] = "-y @modelcontextprotocol/server-everything"
|
||||
}
|
||||
};
|
||||
|
||||
var factory = new McpClientFactory(
|
||||
new[] { config },
|
||||
_defaultOptions
|
||||
);
|
||||
|
||||
var client = await factory.GetClientAsync("everything");
|
||||
// Even numbered resources are binary in the everything server (despite the docs saying otherwise)
|
||||
// 2 is index 1, which is "odd" in the 0-based index
|
||||
var result = await client.ReadResourceAsync("test://static/resource/2", CancellationToken.None);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result.Contents);
|
||||
Assert.NotNull(result.Contents[0].Blob);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ public interface IMcpClient : IAsyncDisposable
|
|||
/// <summary>
|
||||
/// Retrieves a list of available tools from the server.
|
||||
/// </summary>
|
||||
/// <param name="cursor">An optional cursor to paginate the results.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task containing the server's response with tool information.</returns>
|
||||
Task<ListToolsResponse> ListToolsAsync(CancellationToken cancellationToken = default);
|
||||
Task<ListToolsResult> ListToolsAsync(string? cursor = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a tool on the server with optional arguments.
|
||||
|
|
@ -55,6 +56,55 @@ public interface IMcpClient : IAsyncDisposable
|
|||
/// <returns>A task containing the tool's response.</returns>
|
||||
Task<CallToolResponse> CallToolAsync(string toolName, Dictionary<string, object>? arguments = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list of available prompts from the server.
|
||||
/// </summary>
|
||||
/// <param name="cursor">An optional cursor to paginate the results.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task containing the server's response with prompt information.</returns>
|
||||
Task<ListPromptsResult> ListPromptsAsync(string? cursor = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a specific prompt with optional arguments.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the prompt to retrieve</param>
|
||||
/// <param name="arguments">Optional arguments for the prompt</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task containing the prompt's content and messages.</returns>
|
||||
Task<GetPromptResult> GetPromptAsync(string name, Dictionary<string, object>? arguments = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list of available resources from the server.
|
||||
/// </summary>
|
||||
/// <param name="cursor">An optional cursor to paginate the results.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns></returns>
|
||||
Task<ListResourcesResult> ListResourcesAsync(string? cursor = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a resource from the server.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri of the resource.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns></returns>
|
||||
Task<ReadResourceResult> ReadResourceAsync(string uri, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to a resource on the server.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri of the resource.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns></returns>
|
||||
Task SubscribeToResourceAsync(string uri, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from a resource on the server.
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri of the resource.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns></returns>
|
||||
Task UnsubscribeFromResourceAsync(string uri, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a generic JSON-RPC request to the server.
|
||||
/// </summary>
|
||||
|
|
@ -66,6 +116,8 @@ public interface IMcpClient : IAsyncDisposable
|
|||
|
||||
/// <summary>
|
||||
/// Registers a handler for server notifications of a specific method.
|
||||
///
|
||||
/// <see cref="NotificationMethods">Constants for common notification methods</see>
|
||||
/// </summary>
|
||||
/// <param name="method">The notification method to handle.</param>
|
||||
/// <param name="handler">The async handler function to process notifications.</param>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using McpDotNet.Protocol.Messages;
|
|||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using McpDotNet.Utils.Json;
|
||||
using System.Xml.Linq;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class McpClient : IMcpClient
|
||||
|
|
@ -135,13 +136,102 @@ internal class McpClient : IMcpClient
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ListToolsResponse> ListToolsAsync(CancellationToken cancellationToken)
|
||||
public async Task<ListToolsResult> ListToolsAsync(string? cursor = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await SendRequestAsync<ListToolsResponse>(
|
||||
return await SendRequestAsync<ListToolsResult>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "tools/list",
|
||||
// No parameters needed for tools/list
|
||||
Params = cursor != null ? new Dictionary<string, object?> { ["cursor"] = cursor } : null
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ListPromptsResult> ListPromptsAsync(string? cursor = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await SendRequestAsync<ListPromptsResult>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "prompts/list",
|
||||
Params = cursor != null ? new Dictionary<string, object?> { ["cursor"] = cursor } : null
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<GetPromptResult> GetPromptAsync(string name, Dictionary<string, object>? arguments = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await SendRequestAsync<GetPromptResult>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "prompts/get",
|
||||
Params = CreateParametersDictionary(name, arguments)
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ListResourcesResult> ListResourcesAsync(string? cursor = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await SendRequestAsync<ListResourcesResult>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "resources/list",
|
||||
Params = cursor != null ? new Dictionary<string, object?> { ["cursor"] = cursor } : null
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ReadResourceResult> ReadResourceAsync(string uri, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await SendRequestAsync<ReadResourceResult>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "resources/read",
|
||||
Params = new Dictionary<string, object?>
|
||||
{
|
||||
["uri"] = uri
|
||||
}
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task SubscribeToResourceAsync(string uri, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await SendRequestAsync<dynamic>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "resources/subscribe",
|
||||
Params = new Dictionary<string, object?>
|
||||
{
|
||||
["uri"] = uri
|
||||
}
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task UnsubscribeFromResourceAsync(string uri, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await SendRequestAsync<dynamic>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "resources/unsubscribe",
|
||||
Params = new Dictionary<string, object?>
|
||||
{
|
||||
["uri"] = uri
|
||||
}
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
|
|
@ -155,16 +245,27 @@ internal class McpClient : IMcpClient
|
|||
new JsonRpcRequest
|
||||
{
|
||||
Method = "tools/call",
|
||||
Params = new Dictionary<string, object?>
|
||||
{
|
||||
["name"] = toolName,
|
||||
["arguments"] = arguments
|
||||
}
|
||||
Params = CreateParametersDictionary(toolName, arguments)
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> CreateParametersDictionary(string nameParameter, Dictionary<string, object>? optionalArguments = null)
|
||||
{
|
||||
var parameters = new Dictionary<string, object?>
|
||||
{
|
||||
["name"] = nameParameter
|
||||
};
|
||||
|
||||
if (optionalArguments != null)
|
||||
{
|
||||
parameters["arguments"] = optionalArguments;
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private async Task ProcessMessagesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
12
src/mcpdotnet/Protocol/Messages/IJsonRpcMessage.cs
Normal file
12
src/mcpdotnet/Protocol/Messages/IJsonRpcMessage.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for all JSON-RPC messages in the MCP protocol.
|
||||
/// </summary>
|
||||
public interface IJsonRpcMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Must be "2.0".
|
||||
/// </summary>
|
||||
string JsonRpc { get; }
|
||||
}
|
||||
12
src/mcpdotnet/Protocol/Messages/IJsonRpcMessageWithId.cs
Normal file
12
src/mcpdotnet/Protocol/Messages/IJsonRpcMessageWithId.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for JSON-RPC messages that include an ID.
|
||||
/// </summary>
|
||||
public interface IJsonRpcMessageWithId : IJsonRpcMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The message identifier.
|
||||
/// </summary>
|
||||
RequestId Id { get; }
|
||||
}
|
||||
27
src/mcpdotnet/Protocol/Messages/JsonRpcError.cs
Normal file
27
src/mcpdotnet/Protocol/Messages/JsonRpcError.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// An error response message in the JSON-RPC protocol.
|
||||
/// </summary>
|
||||
public record JsonRpcError : IJsonRpcMessageWithId
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Always "2.0".
|
||||
/// </summary>
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; init; } = "2.0";
|
||||
|
||||
/// <summary>
|
||||
/// Request identifier matching the original request.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public required RequestId Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error information.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public required JsonRpcErrorDetail Error { get; init; }
|
||||
}
|
||||
27
src/mcpdotnet/Protocol/Messages/JsonRpcErrorDetail.cs
Normal file
27
src/mcpdotnet/Protocol/Messages/JsonRpcErrorDetail.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Detailed error information for JSON-RPC error responses.
|
||||
/// </summary>
|
||||
public record JsonRpcErrorDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// Integer error code.
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public required int Code { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Short description of the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional additional error data.
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public object? Data { get; init; }
|
||||
}
|
||||
|
|
@ -2,28 +2,6 @@
|
|||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for all JSON-RPC messages in the MCP protocol.
|
||||
/// </summary>
|
||||
public interface IJsonRpcMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Must be "2.0".
|
||||
/// </summary>
|
||||
string JsonRpc { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for JSON-RPC messages that include an ID.
|
||||
/// </summary>
|
||||
public interface IJsonRpcMessageWithId : IJsonRpcMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The message identifier.
|
||||
/// </summary>
|
||||
RequestId Id { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A request message in the JSON-RPC protocol.
|
||||
/// </summary>
|
||||
|
|
@ -53,99 +31,3 @@ public record JsonRpcRequest : IJsonRpcMessageWithId
|
|||
[JsonPropertyName("params")]
|
||||
public object? Params { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A notification message in the JSON-RPC protocol (a request that doesn't expect a response).
|
||||
/// </summary>
|
||||
public record JsonRpcNotification : IJsonRpcMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Always "2.0".
|
||||
/// </summary>
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; init; } = "2.0";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the notification method.
|
||||
/// </summary>
|
||||
[JsonPropertyName("method")]
|
||||
public required string Method { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional parameters for the notification.
|
||||
/// </summary>
|
||||
[JsonPropertyName("params")]
|
||||
public object? Params { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A successful response message in the JSON-RPC protocol.
|
||||
/// </summary>
|
||||
public record JsonRpcResponse : IJsonRpcMessageWithId
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Always "2.0".
|
||||
/// </summary>
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; init; } = "2.0";
|
||||
|
||||
/// <summary>
|
||||
/// Request identifier matching the original request.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public required RequestId Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The result of the method invocation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("result")]
|
||||
public required object Result { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error response message in the JSON-RPC protocol.
|
||||
/// </summary>
|
||||
public record JsonRpcError : IJsonRpcMessageWithId
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Always "2.0".
|
||||
/// </summary>
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; init; } = "2.0";
|
||||
|
||||
/// <summary>
|
||||
/// Request identifier matching the original request.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public required RequestId Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error information.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public required JsonRpcErrorDetail Error { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detailed error information for JSON-RPC error responses.
|
||||
/// </summary>
|
||||
public record JsonRpcErrorDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// Integer error code.
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public required int Code { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Short description of the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional additional error data.
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public object? Data { get; init; }
|
||||
}
|
||||
27
src/mcpdotnet/Protocol/Messages/JsonRpcNotification.cs
Normal file
27
src/mcpdotnet/Protocol/Messages/JsonRpcNotification.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// A notification message in the JSON-RPC protocol (a request that doesn't expect a response).
|
||||
/// </summary>
|
||||
public record JsonRpcNotification : IJsonRpcMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Always "2.0".
|
||||
/// </summary>
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; init; } = "2.0";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the notification method.
|
||||
/// </summary>
|
||||
[JsonPropertyName("method")]
|
||||
public required string Method { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional parameters for the notification.
|
||||
/// </summary>
|
||||
[JsonPropertyName("params")]
|
||||
public object? Params { get; init; }
|
||||
}
|
||||
27
src/mcpdotnet/Protocol/Messages/JsonRpcResponse.cs
Normal file
27
src/mcpdotnet/Protocol/Messages/JsonRpcResponse.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// A successful response message in the JSON-RPC protocol.
|
||||
/// </summary>
|
||||
public record JsonRpcResponse : IJsonRpcMessageWithId
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Always "2.0".
|
||||
/// </summary>
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRpc { get; init; } = "2.0";
|
||||
|
||||
/// <summary>
|
||||
/// Request identifier matching the original request.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public required RequestId Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The result of the method invocation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("result")]
|
||||
public required object Result { get; init; }
|
||||
}
|
||||
27
src/mcpdotnet/Protocol/Messages/NotificationMethods.cs
Normal file
27
src/mcpdotnet/Protocol/Messages/NotificationMethods.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// A class containing constants for notification methods.
|
||||
/// </summary>
|
||||
public static class NotificationMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Sent by the server when the list of tools changes.
|
||||
/// </summary>
|
||||
public const string ToolListChangedNotification = "notifications/tools/list_changed";
|
||||
|
||||
/// <summary>
|
||||
/// Sent by the server when the list of prompts changes.
|
||||
/// </summary>
|
||||
public const string PromptsListChanged = "notifications/prompts/list_changed";
|
||||
|
||||
/// <summary>
|
||||
/// Sent by the server when the list of resources changes.
|
||||
/// </summary>
|
||||
public const string ResourceListChangedNotification = "notifications/resources/list_changed";
|
||||
|
||||
/// <summary>
|
||||
/// Sent by the server when a resource is updated.
|
||||
/// </summary>
|
||||
public const string ResourceUpdatedNotification = "notifications/resources/updated";
|
||||
}
|
||||
12
src/mcpdotnet/Protocol/Messages/PaginatedResult.cs
Normal file
12
src/mcpdotnet/Protocol/Messages/PaginatedResult.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// A paginated result payload.
|
||||
/// </summary>
|
||||
public class PaginatedResult
|
||||
{
|
||||
/// <summary>
|
||||
/// An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.
|
||||
/// </summary>
|
||||
public string? NextCursor { get; set; }
|
||||
}
|
||||
|
|
@ -18,4 +18,5 @@ public record BlobResourceContents : IResourceContents
|
|||
/// </summary>
|
||||
[JsonPropertyName("blob")]
|
||||
public required string Blob { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public class CallToolResponse
|
|||
/// <summary>
|
||||
/// The server's response to a tools/call request from the client.
|
||||
/// </summary>
|
||||
public List<ToolContent> Content { get; set; } = new();
|
||||
public List<Content> Content { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the tool call was unsuccessful. If true, the call was unsuccessful.
|
||||
|
|
|
|||
|
|
@ -1,97 +1,58 @@
|
|||
// Protocol/Types/Content.cs
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of role in the conversation.
|
||||
/// Represents the content of a tool response.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json">See the schema for details</see>
|
||||
/// There are multiple subtypes of content, depending on the "type" field, these are represented as separate classes.
|
||||
/// </summary>
|
||||
public enum Role
|
||||
{
|
||||
[JsonPropertyName("user")]
|
||||
User,
|
||||
|
||||
[JsonPropertyName("assistant")]
|
||||
Assistant
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for content types in messages.
|
||||
/// </summary>
|
||||
public interface IContent
|
||||
{
|
||||
string Type { get; }
|
||||
Annotations? Annotations { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents text content in messages.
|
||||
/// </summary>
|
||||
public record TextContent : IContent
|
||||
public class Content
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of content. Always "text".
|
||||
/// The type of content. This determines the structure of the content object. Can be "image", "text", "resource".
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type => "text";
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The text content of the message.
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public required string Text { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional annotations for the content.
|
||||
/// </summary>
|
||||
[JsonPropertyName("annotations")]
|
||||
public Annotations? Annotations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents image content in messages.
|
||||
/// </summary>
|
||||
public record ImageContent : IContent
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of content. Always "image".
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type => "image";
|
||||
public string? Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The base64-encoded image data.
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public required string Data { get; init; }
|
||||
public string? Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type of the image.
|
||||
/// </summary>
|
||||
[JsonPropertyName("mimeType")]
|
||||
public required string MimeType { get; init; }
|
||||
public string? MimeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional annotations for the content.
|
||||
/// One of BlobResourceContents or TextResourceContents.
|
||||
/// </summary>
|
||||
[JsonPropertyName("annotations")]
|
||||
public Annotations? Annotations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents annotations that can be attached to content.
|
||||
/// </summary>
|
||||
public record Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes who the intended customer of this object or data is.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audience")]
|
||||
public Role[]? Audience { get; init; }
|
||||
public object? Resource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Describes how important this data is for operating the server (0 to 1).
|
||||
/// Validates the content object.
|
||||
/// </summary>
|
||||
[JsonPropertyName("priority")]
|
||||
public float? Priority { get; init; }
|
||||
}
|
||||
/// <returns>Whether the type of tool content has the correct data fields provided.</returns>
|
||||
public bool Validate()
|
||||
{
|
||||
if (Type == "text")
|
||||
{
|
||||
return !string.IsNullOrEmpty(Text);
|
||||
}
|
||||
else if (Type == "image")
|
||||
{
|
||||
return !string.IsNullOrEmpty(Data) && !string.IsNullOrEmpty(MimeType);
|
||||
}
|
||||
else if (Type == "resource")
|
||||
{
|
||||
return Resource is BlobResourceContents || Resource is TextResourceContents;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/mcpdotnet/Protocol/Types/GetPromptResult.cs
Normal file
18
src/mcpdotnet/Protocol/Types/GetPromptResult.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// The server's response to a prompts/get request from the client.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json">See the schema for details</see>
|
||||
/// </summary>
|
||||
public class GetPromptResult
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional description for the prompt.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The prompt or prompt template that the server offers.
|
||||
/// </summary>
|
||||
public List<PromptMessage> Messages { get; set; } = new();
|
||||
}
|
||||
97
src/mcpdotnet/Protocol/Types/IContent.cs
Normal file
97
src/mcpdotnet/Protocol/Types/IContent.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// Protocol/Types/Content.cs
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of role in the conversation.
|
||||
/// </summary>
|
||||
public enum Role
|
||||
{
|
||||
[JsonPropertyName("user")]
|
||||
User,
|
||||
|
||||
[JsonPropertyName("assistant")]
|
||||
Assistant
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for content types in messages.
|
||||
/// </summary>
|
||||
public interface IContent
|
||||
{
|
||||
string Type { get; }
|
||||
Annotations? Annotations { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents text content in messages.
|
||||
/// </summary>
|
||||
public record TextContent : IContent
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of content. Always "text".
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type => "text";
|
||||
|
||||
/// <summary>
|
||||
/// The text content of the message.
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public required string Text { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional annotations for the content.
|
||||
/// </summary>
|
||||
[JsonPropertyName("annotations")]
|
||||
public Annotations? Annotations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents image content in messages.
|
||||
/// </summary>
|
||||
public record ImageContent : IContent
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of content. Always "image".
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type => "image";
|
||||
|
||||
/// <summary>
|
||||
/// The base64-encoded image data.
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public required string Data { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type of the image.
|
||||
/// </summary>
|
||||
[JsonPropertyName("mimeType")]
|
||||
public required string MimeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional annotations for the content.
|
||||
/// </summary>
|
||||
[JsonPropertyName("annotations")]
|
||||
public Annotations? Annotations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents annotations that can be attached to content.
|
||||
/// </summary>
|
||||
public record Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes who the intended customer of this object or data is.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audience")]
|
||||
public Role[]? Audience { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Describes how important this data is for operating the server (0 to 1).
|
||||
/// </summary>
|
||||
[JsonPropertyName("priority")]
|
||||
public float? Priority { get; init; }
|
||||
}
|
||||
15
src/mcpdotnet/Protocol/Types/ListPromptsResult.cs
Normal file
15
src/mcpdotnet/Protocol/Types/ListPromptsResult.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using McpDotNet.Protocol.Messages;
|
||||
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// The server's response to a prompts/list request from the client.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json">See the schema for details</see>
|
||||
/// </summary>
|
||||
public class ListPromptsResult : PaginatedResult
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of prompts or prompt templates that the server offers.
|
||||
/// </summary>
|
||||
public List<Prompt> Prompts { get; set; } = new();
|
||||
}
|
||||
15
src/mcpdotnet/Protocol/Types/ListResourcesResult.cs
Normal file
15
src/mcpdotnet/Protocol/Types/ListResourcesResult.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using McpDotNet.Protocol.Messages;
|
||||
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// The server's response to a resources/list request from the client.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json">See the schema for details</see>
|
||||
/// </summary>
|
||||
public class ListResourcesResult : PaginatedResult
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of resources that the server offers.
|
||||
/// </summary>
|
||||
public List<Resource> Resources { get; set; } = new();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue