mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
## Summary This PR introduces the **1chat MCP server** in Kibana, exposed at the experimental `/api/mcp` endpoint behind a feature flag. It allows external MCP clients (e.g. Claude Desktop, Cursor, OpenAI Agents) to connect and use tools registered in the 1chat registry. ### MCP server - Implements a **stateless** MCP server following the MCP spec (Streamable HTTP transport). - Supports **API key** and **basic auth** for authentication. - Works with clients via: - **Streamable HTTP** with auth header - **STDIO** transport using `mcp-remote` proxy - Endpoint under a feature flag `xpack.onechat.mcpServer.enabled` - 1chat tools are scoped to the caller’s permissions, as determined by the auth header. ### Other changes - Implemented `KibanaMcpHttpTransport` (mcp http transport layer adapted to Kibana Core primitives) + tests ### Local testing Set ui setting: `onechat:mcpServer:enabled` to true E.g. add this to Claude Desktop: ``` { "mcpServers": { "elastic": { "command": "npx", "args": [ "mcp-remote", "https://{kbn}/api/mcp", "--header", "Authorization: ApiKey ${API_KEY}" ], "env": { "API_KEY": "..." } }, } } ``` ### Enable feature via API ``` POST kbn:/internal/kibana/settings/onechat:mcpServer:enabled {"value": true} ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
/*
|
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
* or more contributor license agreements. Licensed under the Elastic License
|
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
* 2.0.
|
|
*/
|
|
|
|
import type { Logger } from '@kbn/logging';
|
|
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server';
|
|
import { i18n } from '@kbn/i18n';
|
|
import { schema } from '@kbn/config-schema';
|
|
import type { OnechatConfig } from './config';
|
|
import type {
|
|
OnechatPluginSetup,
|
|
OnechatPluginStart,
|
|
OnechatSetupDependencies,
|
|
OnechatStartDependencies,
|
|
} from './types';
|
|
import { registerRoutes } from './routes';
|
|
import { ServiceManager } from './services';
|
|
import { registerFeatures } from './features';
|
|
import { ONECHAT_MCP_SERVER_UI_SETTING_ID } from '../common/constants';
|
|
|
|
export class OnechatPlugin
|
|
implements
|
|
Plugin<
|
|
OnechatPluginSetup,
|
|
OnechatPluginStart,
|
|
OnechatSetupDependencies,
|
|
OnechatStartDependencies
|
|
>
|
|
{
|
|
private logger: Logger;
|
|
// @ts-expect-error unused for now
|
|
private config: OnechatConfig;
|
|
private serviceManager = new ServiceManager();
|
|
|
|
constructor(context: PluginInitializerContext<OnechatConfig>) {
|
|
this.logger = context.logger.get();
|
|
this.config = context.config.get();
|
|
}
|
|
|
|
setup(
|
|
coreSetup: CoreSetup<OnechatStartDependencies, OnechatPluginStart>,
|
|
pluginsSetup: OnechatSetupDependencies
|
|
): OnechatPluginSetup {
|
|
const serviceSetups = this.serviceManager.setupServices({
|
|
logger: this.logger.get('services'),
|
|
});
|
|
|
|
registerFeatures({ features: pluginsSetup.features });
|
|
|
|
coreSetup.uiSettings.register({
|
|
[ONECHAT_MCP_SERVER_UI_SETTING_ID]: {
|
|
description: i18n.translate('onechat.uiSettings.mcpServer.description', {
|
|
defaultMessage: 'Enables MCP server with access to tools.',
|
|
}),
|
|
name: i18n.translate('onechat.uiSettings.mcpServer.name', {
|
|
defaultMessage: 'MCP Server',
|
|
}),
|
|
schema: schema.boolean(),
|
|
value: false,
|
|
readonly: true,
|
|
readonlyMode: 'ui',
|
|
},
|
|
});
|
|
|
|
const router = coreSetup.http.createRouter();
|
|
registerRoutes({
|
|
router,
|
|
coreSetup,
|
|
logger: this.logger,
|
|
getInternalServices: () => {
|
|
const services = this.serviceManager.internalStart;
|
|
if (!services) {
|
|
throw new Error('getInternalServices called before service init');
|
|
}
|
|
return services;
|
|
},
|
|
});
|
|
|
|
return {
|
|
tools: {
|
|
register: serviceSetups.tools.register.bind(serviceSetups.tools),
|
|
registerProvider: serviceSetups.tools.registerProvider.bind(serviceSetups.tools),
|
|
},
|
|
};
|
|
}
|
|
|
|
start(
|
|
{ elasticsearch, security }: CoreStart,
|
|
{ actions, inference }: OnechatStartDependencies
|
|
): OnechatPluginStart {
|
|
const startServices = this.serviceManager.startServices({
|
|
logger: this.logger.get('services'),
|
|
security,
|
|
elasticsearch,
|
|
actions,
|
|
inference,
|
|
});
|
|
|
|
const { tools, agents, runnerFactory } = startServices;
|
|
const runner = runnerFactory.getRunner();
|
|
|
|
return {
|
|
tools: {
|
|
registry: tools.registry.asPublicRegistry(),
|
|
execute: runner.runTool.bind(runner),
|
|
asScoped: ({ request }) => {
|
|
return {
|
|
registry: tools.registry.asScopedPublicRegistry({ request }),
|
|
execute: (args) => {
|
|
return runner.runTool({ ...args, request });
|
|
},
|
|
};
|
|
},
|
|
},
|
|
agents: {
|
|
registry: agents.registry.asPublicRegistry(),
|
|
execute: async (args) => {
|
|
return agents.execute(args);
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
stop() {}
|
|
}
|