-
Notifications
You must be signed in to change notification settings - Fork 571
Initial feedback on McpClient NO MERGE #1001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| ModelContextProtocol.Protocol.Implementation name is too generic. Lots of things in this namespace named poorly. That might be OK if users don't have to deal with them, but it looks like they do. | ||
|
|
||
|
|
||
| ModelClient.CompleteAsync - naming seems off. Maybe "GetCompletion" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, it's following the naming used by the MCP method: completion/complete
|
|
||
|
|
||
|
|
||
| ModelClient - Enumerate\* vs List\* - why have these redundant methods that return the same thing just one wrapping. Can we just return IAE and have folks materialize that? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The List one is the vastly most common need, to actually get back an IList. IIRC, @eiriktsarpalis also thought it would be desirable to have the EnumerateXxAsync ones. I wouldn't want to drop the ListXxAsync ones, as that would be penalizing the 90% case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see - do we follow this pattern elsewhere? I just tripped up on it and thought breifly about which was right/better and if they were different. An alternate naming scheme might be
GetAsync and GetAsEnumerableAsync - that makes the less common one less attractive in names.
|
|
||
|
|
||
|
|
||
| ModelClient.EnumerateToolsAsync - why accept JsonSerializerOptions on this call, it doesn't feel like something tied to enumerating tools. Would it make more sense to make this a property on the client? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It gets stored into the create McpClientTool instance (McpClientTool : AIFunction), where it's needed for serializing arguments/results when the AIFunction is invoked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get it, but why pass on the method? Do folks really provide a different one per call? Would it make more sense as a property on the client to be used as needed by the methods?
|
|
||
|
|
||
|
|
||
| SetLoggingLevel(LogLevel, CancellationToken) vs SetLoggingLevel(LoggingLevel, CancellationToken) -- WHY? Don't make these overloads, instead choose one then make conversions on the types. |
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
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.
|
|
||
|
|
||
|
|
||
| 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. |
There was a problem hiding this comment.
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); |
|
|
||
| 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
|
|
||
|
|
||
|
|
||
| Odd that ResourceContents is plural, but ContentBlock is not. Why do we even need to different sets of types for these? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They map 1:1 with concepts in the MCP schema / spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see - do you think it makes sense for asking the MCP folks to unify their content types?
|
|
||
|
|
||
|
|
||
| Similarly ReadResourceResult has Contents, while CallToolResult has Content, but both are ILists. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can double-check to confirm, but I believe the naming here is just copying exactly what MCP has (other than capitalization).
| 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. |
There was a problem hiding this comment.
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.
@stephentoub @halter73 @mikekistler @jeffhandley