Skip to content

Conversation

@ericstj
Copy link
Contributor

@ericstj ericstj commented Nov 20, 2025




SetLoggingLevel(LogLevel, CancellationToken) vs SetLoggingLevel(LoggingLevel, CancellationToken) -- WHY? Don't make these overloads, instead choose one then make conversions on the types.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoggingLevel is the MCP type, LogLevel is the M.E.Logging type. Why shouldn't these be overloads? LoggingLevel has more values than LogLevel, so anyone with a LogLevel would be forced to call ToLoggingLevel as part of calling this. Why is that better than having it "just work"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With an enum like this aren't folks directly calling it with a literal? Why make them choose which one to use? The reason I wrote this was because I saw both in intellisense and wondered which I should use and why they were different. Having both sets the precedent for adding more. If we choose one as the currency of this type it establishes a pattern without any promise of more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With an enum like this aren't folks directly calling it with a literal?

I'd expect it would more commonly be read from configuration or otherwise be dynamic rather than being hardcoded to a literal at the call site.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will scan for any other public surface that uses MCP primitives where we might have an ME/MEAI primitive as well.




SubscribeToResourceAsync - how are the notifications delivered? This method confused me as I don't understand what it's supposed to do. Might just need to research more.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MCP has a notifications mechanism. The client can set up a notification handler for specific notifications.

public abstract IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs better docs to connect the two methods at least. I wonder if naming could be improved to make it more obvious that the methods are complimentary.

I think I understand what to pass SubscribeToResourceAsync - that's the resource "path", but no clue what I should pass for method to RegisterNotificationHandler based on those docs. Is it just any of these

and the the combination of methods subscribed + resource subscribed to determines when the handler is invoked?

It's unusual that this method has subscribe+unsubscribe, yet RegisterNotificationHandler just returns an IAsyncDisposable. Seems inconsistent.

I also see tests that are specifying Handlers when creating the client.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs better docs to connect the two methods at least.

We need lots more docs but it does seem this should be high on the list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could make Subscribe return an IAsyncDisposable. That could unsubscribe.
Could add an overload to Subscribe that takes a handler and registers it. The dispose from that would both unsubscribe and unregister.
@ericstj to file an issue


TextContentBlock doesn't override ToString -- to get any of the data returned I need to get at protocol types, which feels wrong since those are all "raw" and not designed surface area.

Same is true for TextResourceContents. The entire content model seems pretty rough - might be better to unify on MEAI content types.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe relying only on the MEAI types is viable. They're good for passing a 90% representation around, but they will never be full-fidelity with every provider.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's fair. It would put pressure on us to add more types to MEAI if we didn't support the scenario, but it could create a common set of things that the ecosystem might build upon. We can always do that later I suppose. Perhaps we can build conversion extensions now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can build conversion extensions now.

We have those, e.g.

public static AIContent? ToAIContent(this ContentBlock content)

public static AIContent ToAIContent(this ResourceContents content)

public static ContentBlock ToContentBlock(this AIContent content)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see - do we want to tell folks this is the preferred way to deal with content? I presume we still want the MCP content types to work (without using protocol types).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see - do we want to tell folks this is the preferred way to deal with content?

In what context? If you're just coding against the MCP library directly, you'd typically use the MCP types. You'd use the conversion utilities when needing to interoperate across library boundaries. For example, the McpClientTool's AIFunction.InvokeAsync override converts the MCP types to AIContent so that other components in an IChatClient pipeline can consume the content readily.

BlobResourceContents exposes content as a normal string, which means it encoded the UTF-8 to a string, creating work for GC. Instead it should keep the UTF8 contents, and lazily decode that from base64 UTF8 to bytes (either as stream, or byte array). The same problem exists with ContentBlock types, where base-64 data is exposed as Unicode strings.


Prompts expose arguments as types, whereas tools expose arguments as json schema.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tools in MCP expose JSON schema. Prompts do not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but both are describing typing of arguments on the same McpClient - it feels unusual that they have different technologies for the same/similar task.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate? What do you mean by "Prompts expose arguments as types"? There's no type information for prompts in the MCP specification, e.g.

public sealed class PromptArgument : IBaseMetadata
{
/// <inheritdoc />
[JsonPropertyName("name")]
public required string Name { get; set; }
/// <inheritdoc />
[JsonPropertyName("title")]
public string? Title { get; set; }
/// <summary>
/// Gets or sets a human-readable description of the argument's purpose and expected values.
/// </summary>
/// <remarks>
/// This description helps developers understand what information should be provided
/// for this argument and how it will affect the generated prompt.
/// </remarks>
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether this argument must be provided when requesting the prompt.
/// </summary>
/// <remarks>
/// When set to <see langword="true"/>, the client must include this argument when making a <see cref="RequestMethods.PromptsGet"/> request.
/// If a required argument is missing, the server should respond with an error.
/// </remarks>
[JsonPropertyName("required")]
public bool? Required { get; set; }
}
. In contrast, tools in the MCP specification expose an input schema for all parameters, e.g.
public JsonElement InputSchema
. They're different in the MCP library because they're different in the MCP spec/protocol.

SubscribeToResourceAsync - how are the notifications delivered? This method confused me as I don't understand what it's supposed to do. Might just need to research more.


TextContentBlock doesn't override ToString -- to get any of the data returned I need to get at protocol types, which feels wrong since those are all "raw" and not designed surface area.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jeffhandley -- here's an example of one of the "need to use Protocols" to do a basic thing. I didn't do a deep scrub of everything, but this one was a very basic scenario

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this proposing? Not exposing any of the of the protocol types in various APIs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To use these we have to downcast. There is no DebuggerDisplay to help folks understand what they have in the IDE. We could make these more closely match the pattern used in AIContent. @ericstj to file issue - DebuggerDisplay and ToString.




BlobResourceContents exposes content as a normal string, which means it encoded the UTF-8 to a string, creating work for GC. Instead it should keep the UTF8 contents, and lazily decode that from base64 UTF8 to bytes (either as stream, or byte array). The same problem exists with ContentBlock types, where base-64 data is exposed as Unicode strings.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like something we control and ought to resolve before going stable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj to open an issue for properties that don't force folks to decode a string. Expose raw, and lazy byte[]

@ericstj ericstj changed the title Initial feedback on McpClient NO MERGE Feedback on McpClient NO MERGE Dec 2, 2025
@jeffhandley jeffhandley added this to the Stable public API milestone Dec 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants