-
-
Notifications
You must be signed in to change notification settings - Fork 23
Add support for MCP prompts #336
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
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (19)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the ✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
it looks good and reuse the pattern from tools, so it's as easy and same way of using it. |
72469eb
to
59c00cd
Compare
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.
looks good overall!
{ | ||
PromptMetadata promptMetadata = (PromptMetadata)promtps[promptName]; | ||
MethodInfo method = promptMetadata.Method; | ||
Console.WriteLine($"Prompt name: {promptName}, method: {method.Name}"); |
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.
debug rather than console
public static string AnotherSimplePrompt() | ||
{ | ||
return "This is another prompt example"; | ||
} |
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.
maybe add one with 1 param
5355efd
to
cbc1db4
Compare
- Add prompt registry class and methods. - Add prompt message, prompt parameter and roles classes. - Extract common methods to RegistryBase class. - Adjust McpToolRegistry accordingly. - Add tests for simple prompts. - Update MCP client test to list available prompts.
cbc1db4
to
e64bc4e
Compare
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.
few more thigs but looks good overall
/// <inheritdoc/> | ||
public override string ToString() | ||
{ | ||
return $"{{\"name\":\"{Name}\",\"description\":\"{Description}\",\"required\":\"true\"}}"; |
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.
in the case you have multiple prompts in the same function, you've set them all to be required. Not sure it can smoothly work in that case. Maybe only 1 can be mandatory or leaving the choice in the attribute (with true as default)
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.
As the parameter name is not available in reflection, there is no way to tell which parameter is which, meaning no clean way to identify the parameters. That's why I've added this "by design" rule: if a parameter is declared it has to be mandatory and included in the call.
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.
couple of suggestions to have all working properly
@@ -46,7 +46,14 @@ | |||
// Load them as AI functions in the kernel | |||
#pragma warning disable SKEXP0001 | |||
kernel.Plugins.AddFromFunctions("nanoFramework", tools.Select(aiFunction => aiFunction.AsKernelFunction())); | |||
// -- | |||
|
|||
// Check available prompts |
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 use this instead those lines:
//
// 4. Register prompts from MCP
//
try
{
var prompts = await mcpToolboxClient.ListPromptsAsync().ConfigureAwait(false);
Console.WriteLine("// Available prompts:");
foreach (var p in prompts)
{
Console.WriteLine($"{p.Name}: {p.Description}");
var promptResult = await mcpToolboxClient.GetPromptAsync(p.Name, new Dictionary<string, object>());
var promptTemplate = string.Join("\n", promptResult.Messages.Select(m => m.Content));
var semanticFunction = KernelFunctionFactory.CreateFromPrompt(
promptTemplate: promptTemplate,
executionSettings: (PromptExecutionSettings?)null, // Explicit cast to resolve ambiguity
functionName: p.Name,
description: p.Description,
templateFormat: "semantic-kernel"
);
kernel.Plugins.AddFromFunctions(p.Name, new[] { semanticFunction });
}
Console.WriteLine("// --");
}
catch (Exception ex)
{
Console.WriteLine($"Error loading prompts: {ex.Message}");
}
{ | ||
return new PromptMessage[] | ||
{ | ||
new PromptMessage($"Please perform the following steps:\r\n1. Call GetDefaultPerson() -> person.\r\n2. Call GetDefaultAddress() -> address.\r\n3. If person.Age > {ageThreshold} then set label = \"senior\"; otherwise set label = \"junior\".\r\n4. Call ProcessPerson(person) -> processed.\r\n5. Return a JSON object:\r\n{{ \"name\": person.Name, \"age\": person.Age, \"label\": label, \"address\": address, \"processed\": processed }}") |
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.
everything needs to be string encoded. so, this will break. This will actually work (or manual encoding):
new PromptMessage($"Please perform the following steps:\r\n1. Call GetDefaultPerson() -> person.\r\n2. Call GetDefaultAddress() -> address.\r\n3. If person.Age > {ageThreshold} then set label = \"senior\"; otherwise set label = \"junior\".\r\n4. Call ProcessPerson(person) -> processed.\r\n5. Return a JSON object:\r\n{{ \"name\": person.Name, \"age\": person.Age, \"label\": label, \"address\": address, \"processed\": processed }}") | |
new PromptMessage(JsonSerializer.SerializeObject($"Please perform the following steps:\r\n1. Call GetDefaultPerson() → person.\r\n2. Call GetDefaultAddress() → address.\r\n3. If person.Age > {ageThreshold ?? string.Empty} then set label = \"senior\"; otherwise set label = \"junior\".\r\n4. Call ProcessPerson(person) → processed.\r\n5. Return a JSON object:\r\n{{ \"name\": person.Name, \"age\": person.Age, \"label\": label, \"address\": address, \"processed\": processed }}").TrimStart('"').TrimEnd('"')) |
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.
For performance, I would prefer a manual encoded string, so there is no need to call the Json serializer.
arguments.Values.CopyTo(methodParams, 0); | ||
} | ||
|
||
PromptMessage[] result = (PromptMessage[])method.Invoke(null, methodParams); |
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.
As the LLM does not really necessarily respect the number of arguments, we can do something like this:
PromptMessage[] result = (PromptMessage[])method.Invoke(null, methodParams); | |
PromptMessage[] result; | |
try | |
{ | |
result = (PromptMessage[])method.Invoke(null, methodParams); | |
} | |
catch (ArgumentException) | |
{ | |
// Trying with 1 parameter as LLM may have forgot it | |
result = (PromptMessage[])method.Invoke(null, new object[] { null }); | |
} |
|
Description
Motivation and Context
How Has This Been Tested?
Screenshots
Types of changes
Checklist: