Skip to content

Conversation

@ppisljar
Copy link
Contributor

@ppisljar ppisljar commented Oct 27, 2025

Summary

This PR implements Model Context Protocol (MCP) support in Kibana using the connector framework with full
Agent Builder integration. MCP connectors enable Kibana agents to access external tools from MCP-compliant
servers, expanding agent capabilities beyond built-in tools.

Key Highlights:

  • New .mcp connector type
  • Full Agent Builder integration with tool discovery and execution
  • Secure authentication with proper credential encryption (fixed security vulnerability)
  • Tool List caching

Related Work

Future Work (Not in This PR)

  • OAuth Authentication: OAuth 2.0 with PKCE support (Plan 003)

Screenshots
Agent-Builder-Elastic (1)
Agent-Builder-Elastic
Connectors-Elastic (2)
Connectors-Elastic (1)


Summary: MCP connector support to Kibana, enabling Agent Builder agents to leverage external
tools from MCP servers.

@ppisljar ppisljar added backport:skip This PR does not require backporting release_note:feature Makes this part of the condensed release notes v9.3.0 labels Oct 27, 2025
Copy link
Contributor

@pgayvallet pgayvallet left a comment

Choose a reason for hiding this comment

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

Overall approach looks fine to me.

Bunch of questions and other things though

@@ -0,0 +1,3 @@
# @kbn/mcp-connector-common

Empty package generated by @kbn/generate
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: dummy readme content

Comment on lines +19 to +21
expect(isMcpToolId('mcp.github.get_issues')).toBe(true);
expect(isMcpToolId('mcp.slack.send_message')).toBe(true);
expect(isMcpToolId('mcp.my-connector.tool-name')).toBe(true);
Copy link
Contributor

@pgayvallet pgayvallet Oct 28, 2025

Choose a reason for hiding this comment

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

We need to block the mcp. prefix for user-created tools - having gone through the PR I don't think we are

/**
* Validate that a tool id has the right format,
* returning an error message if it fails the validation,
* and undefined otherwise.
*
* @param toolId: the toolId to validate
* @param builtIn: set to true if we're validating a built-in (internal) tool id.
*/
export const validateToolId = ({
toolId,
builtIn,
}: {
toolId: string;
builtIn: boolean;
}): string | undefined => {

Comment on lines +177 to +180
profileUpdate.configuration.tools = await this.validateAgentToolSelection(
profileUpdate.configuration.tools,
{ autoFilter: true }
);
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. What are we trying to do exactly here? Is this meant to be to remove old MCP tools or something?
  2. Why only on update and not on create?

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 is for the case where agent was given access to some tools that are no longer there (someone deleted the connector)

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 think we'll be able to remove this as @jedrazb addressed this already


return {
id: provider.id,
type: ToolType.builtin,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we want a dedicated mcp tool type.

type: ToolType.builtin,
description: description || `Tool: ${name}`,
readonly: true,
tags: ['mcp', `connector:${connectorId}`],
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: should we use the provider name instead of the connectorId here? Those are uuids which are quite ugly displayed as tags. We can also just remove the connector:xxx tag.

Comment on lines +151 to +157
async function listAllMcpTools({
actionsClient,
logger,
}: {
actionsClient: ActionsClient;
logger: Logger;
}): Promise<InternalToolDefinition[]> {
Copy link
Contributor

@pgayvallet pgayvallet Oct 28, 2025

Choose a reason for hiding this comment

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

This is fine an initial approach, but as discussed we gonna want something slightly more robust later, like som active sync of the connector/tools list to avoid resolving them at every request.

Maybe just some naive caching around the calls to discoverToolsFromConnector with a Xmins TTL could already go a long way?

EDIT: saw the cache at the connector level - which I think is fine for now - can forget about this

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 think we want cache on the connector level so everyone can benefit from it.

class ToolRegistryImpl implements ToolRegistry {
private readonly persistedProvider: WritableToolProvider;
private readonly builtinProvider: ReadonlyToolProvider;
private readonly mcpProvider?: ReadonlyToolProvider;
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: why optional (looking at the service it's always provided, isn't it?)


private connected: boolean = false;

private cachedTools: ListToolsResponse | null = null; // Instance cache
Copy link
Contributor

Choose a reason for hiding this comment

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

FMI - Have you tested it, does it actually work that way? I always thought the connector instance was re-created for every sub action call? But given that class registers the sub actions, I realize I was probably wrong.

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 found this pattern in crowdstrike connector, i didnt test this much yet, but from the logs it looked like its not firing tool discovery requests at every conversation round.

Comment on lines 132 to 136
this.registerSubAction({
method: 'listTools',
name: 'listTools',
schema: null,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

If we think we're fine with that in-connector cache approach (and it sounds okay for now), then I would add a parameter to the listTools subaction to force a cache clear, wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lets defer this until needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

actually i'll add this as it helps me test the flow

Comment on lines +106 to +112
const transport = new StreamableHTTPClientTransport(new URL(service.http.url), {
requestInit: {
headers,
},
});

await this.client.connect(transport);
Copy link
Contributor

Choose a reason for hiding this comment

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

My gut feeling tells me that this connect logic is too simple, that keeping the client open is a bit naive, and that we should probably close it after some time, and at least handle reconnects (if the server terminates the connection on their side, atm this explodes, no?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what i wanted to do is connect everytime we need to request tool list (so it should not be keeping the connection open). so it should actually only connect when cache is missed and then get the tool list and close the connection (so no need for complex connection management) ....

possibly i messed this up, will take a look and also do some more testing,

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 tested this and it works as expected, connections are always recreated/closed, no long running connections

@ppisljar ppisljar force-pushed the agentbuilder/mcp_connector branch from 968e1ab to e3ac428 Compare October 30, 2025 14:32
@ppisljar ppisljar marked this pull request as ready for review October 30, 2025 15:46
@ppisljar ppisljar requested review from a team as code owners October 30, 2025 15:46
@adcoelho
Copy link
Contributor

Since this is a new connector type, the PR should be split into 2 according to the intermediate release process.

There is an example here for the Jira Service Management Connector:

You can reach out in #kibana-alerting for more info 🙌

@elasticmachine
Copy link
Contributor

elasticmachine commented Nov 5, 2025

💔 Build Failed

Failed CI Steps

History

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting release_note:feature Makes this part of the condensed release notes v9.3.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants