Ts.ED gateway
Use Ts.ED to expose OAuth-protected MCP endpoints: one Kibana server per environment, one catalog per user.
Run the Kibana MCP server
- Create one config per Kibana instance:
ts
// kibana.production.config.ts
import { defineKibanaMcpConfig } from "@havesomecode/kibana-mcp-server/config";
import store from "@havesomecode/kibana-mcp-server/stores/redis";
export default defineKibanaMcpConfig({
url: process.env.KIBANA_PRODUCTION_BASE_URL,
user: process.env.KIBANA_USERNAME,
password: process.env.KIBANA_PASSWORD,
store,
storeOptions: { url: process.env.REDIS_URL, keyPrefix: "kibana-mcp:production:" },
http: {
port: 3101,
profileHeader: "x-kibana-profile",
authToken: process.env.KIBANA_MCP_TOKEN,
},
});- Start production:
bash
kibana-mcp-server serve --http --config ./kibana.production.config.ts- For staging, use its own URL, port
3102, and prefixkibana-mcp:staging:.
Declare the gateway
Set the profile from the verified token subject. Ts.ED interpolates ${OAUTH_SUB}; keep placeholders as plain strings, not template literals. Overwrite client-supplied profile headers; never trust them.
ts
import type { PlatformMcpSettings } from "@tsed/platform-mcp";
export function kibanaGateway(name: string, path: string, upstreamUrl: string): PlatformMcpSettings {
return {
name,
path,
auth: {
issuer: process.env.OIDC_ISSUER,
resource: new URL(path, process.env.PORTAL_BASE_URL).toString(),
mode: "introspection",
clientId: process.env.OIDC_CLIENT_ID,
clientSecret: process.env.OIDC_CLIENT_SECRET,
requiredScopes: ["api_admin"],
},
upstream: {
type: "http",
url: upstreamUrl,
headers: {
Authorization: `Bearer ${process.env.KIBANA_MCP_TOKEN}`,
"X-Kibana-Profile": "${OAUTH_SUB}",
},
},
};
}
export const mcpsConfig: PlatformMcpSettings[] = [
kibanaGateway("kibana", "/mcp/kibana/production", "http://127.0.0.1:3101/mcp"),
kibanaGateway("kibana_staging", "/mcp/kibana/staging", "http://127.0.0.1:3102/mcp"),
];What each user gets
Expected: a catalog per token subject; queries use the shared Kibana service account. New catalogs are empty. Call discover, ask for the exact index or pattern, and wait before configure_index. Never guess, list, or scan indexes.
Things to check
- Reject tokens without a string
sub, or assign a subject in Ts.ED.${OAUTH_SUB}is otherwise empty and the server returns401; checkclient_credentialsand custom pre-authentication results. - Use
httpfor one MCP process shared across users. Astdioupstream starts one process per token.