-
Notifications
You must be signed in to change notification settings - Fork 102
MCP support #17
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
MCP support #17
Conversation
1978c25
to
18a05fb
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.
Overall looks nice, there are a few things I would like to discuss before merging: working with resources and tests
) : Tool<McpTool.Args, McpTool.Result>() { | ||
|
||
companion object { | ||
private val logger = LoggerFactory.create("ai.grazie.code.agents.mcp.McpTool") |
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.
Not used?
is JsonDecoder -> { | ||
// Decode directly from JsonElement | ||
val jsonElement = decoder.decodeJsonElement() | ||
Args(jsonElement as JsonObject) |
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.
jsonElement.jsonObject
is better
*/ | ||
override fun toStringDefault(): String { | ||
if (promptMessageContents.isEmpty()) { | ||
return "[No content]" |
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 empty string is better?
val result = mcpClient.callTool( | ||
name = descriptor.name, arguments = args.arguments | ||
) | ||
return Result(result?.content ?: emptyList()) |
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.
content.orEmpty()
is a bit more cleaner
// Create a ToolDescriptor | ||
return ToolDescriptor( | ||
name = sdkTool.name, | ||
description = sdkTool.description ?: "", |
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.
Again, .orEmpty()
* @property itemsType The type definition for the items within the array. | ||
*/ | ||
data class List(val itemsType: ToolParameterType) : ToolParameterType("ARRAY") | ||
|
||
/** | ||
* Represents an array type parameter. |
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.
Please fix doc comment, it represents and object, actually. But that's a good addition, we need to support objects
@@ -39,6 +35,19 @@ fun ToolDescriptor.toJSONSchema(): JsonObject { | |||
put("type", "array") | |||
put("items", toolParameterToSchema(type.itemsType)) | |||
} | |||
|
|||
is ToolParameterType.Object -> { |
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.
Seems like this whole function is not used anywhere, can we just drop it?
@@ -441,6 +441,20 @@ open class AnthropicDirectLLMClient( | |||
"items" to getTypeMapForParameter(type.itemsType) | |||
) | |||
) | |||
|
|||
is ToolParameterType.Object -> JsonObject( |
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.
Just to confirm, did you test that all providers support these nested types (objects) in their tool api?
* 3. Transforming MCP tools into the agent framework's Tool interface | ||
* 4. Registering the transformed tools in a ToolRegistry | ||
*/ | ||
class McpToolRegistryProvider { |
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.
Also, I think we need tests for it, if it's possible
* 2. Calling the MCP tool through the MCP client | ||
* 3. Converting MCP tool results back to agent framework tool results | ||
*/ | ||
class McpTool( |
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 think it would be nice to add test for it too, if It's possible
c0fdc58
to
84bf27a
Compare
I see that we're supporting using the mcp tools, but currently not supporting the agent being mcp server? Do we plan it/want it at all? |
* @param element The JSON object representing the parameter type. | ||
* @return The corresponding ToolParameterType, or null if the type cannot be parsed. | ||
*/ | ||
private fun parseParameterType(element: JsonObject): ToolParameterType? { |
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 would move all the parsing into the separate class, just in case we (or our users) would want to change parsing logic
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 also thought about somethign like this, but I'm not sure if this is the real case, since MCP format is kinda fixed, no? But giving more flexibility is still a good think, so I agree that we can think in this direction too
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 mean it's just rule of thumb, MCP for now is fixed, but world is changing and we're not safe from MCPv2 someday
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.
Yes, good points, as I already wrote, giving more flexibility and providing some sane default implementation can be nice
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.
Created a separate parser interface with default implementation, where I moved this logic McpToolDescriptorParser
// Get the API key from environment variables | ||
val openAIApiToken = System.getenv("OPEN_AI_TOKEN") | ||
|
||
// Start the Docker container with the Google Maps MCP server |
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's Playwright
).start() | ||
|
||
// Wait for the server to start | ||
Thread.sleep(2000) |
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.
Well, it's not very robust, because app start time heavily varies on user's machine and we could improve it here, for example waiting to socket to be open, but it's optional for 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.
Since this is just in examples, let's leave it like this for now. We can add more complex logic later
is ToolParameterType.Object -> { | ||
put("type", JsonPrimitive("object")) | ||
put("properties", buildJsonObject { | ||
type.properties.forEach { property -> | ||
put(property.name, buildJsonObject { | ||
fillOpenRouterParamType(property.type) | ||
put("description", property.description) | ||
}) | ||
} | ||
} | ||
) |
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 suggest to move string literals to consts, here and all over OpenRouterDirectLLMClient
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.
Let's address this in a separate PR
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.
We've improved API and added tests. Seems like in the current state we can merge it and work on additional features in the separate PRs
bcb3e66
to
3e4ad38
Compare
…c to default implementation
c8ab66e
to
93827a3
Compare
MCP support:
Added
McpToolRegistryProvider
which instantiates mcp client (or gets as an argument), connects to it, get tools and convert to out tool format. The tool execute method calls mcp server with given args (provided in rawJsonObject
as we can not build exact kotlin data class for them, see serialization)Supported two types of connection stdio and sse, all for now in jvm module, but potentially sse can be multiplatform