elastic-apm-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # elastic-apm-mcp-server
2
+
3
+ Kibana APM API를 통해 서비스 목록, 트랜잭션 성능, 에러를 조회하는 MCP 서버입니다.
4
+
5
+ ## 요구사항
6
+
7
+ - Node.js 18 이상
8
+ - Kibana 8.x (내부 APM API를 사용하므로 버전에 따라 동작이 달라질 수 있습니다)
9
+
10
+ ## 설정
11
+
12
+ Claude Desktop 또는 Claude Code 설정에 다음을 추가합니다. 별도 설치 없이 `npx`로 바로 실행됩니다.
13
+
14
+ ### Claude Code
15
+
16
+ `~/.claude/settings.json` 또는 프로젝트의 `.claude/settings.json`에 추가:
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "elastic-apm": {
22
+ "command": "npx",
23
+ "args": ["-y", "elastic-apm-mcp-server"],
24
+ "env": {
25
+ "KIBANA_URL": "http://your-kibana-host:5601",
26
+ "KIBANA_USERNAME": "your-username",
27
+ "KIBANA_PASSWORD": "your-password"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### Claude Desktop
35
+
36
+ `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) 에 동일한 형식으로 추가합니다.
37
+
38
+ ### 환경변수
39
+
40
+ | 환경변수 | 필수 | 설명 |
41
+ |----------|------|------|
42
+ | `KIBANA_URL` | O | Kibana 접속 URL |
43
+ | `KIBANA_USERNAME` | O | Kibana 사용자 이름 |
44
+ | `KIBANA_PASSWORD` | O | Kibana 비밀번호 |
45
+
46
+ ## 제공 도구
47
+
48
+ | 도구 | 설명 |
49
+ |------|------|
50
+ | `list_services` | APM에 등록된 서비스 목록 조회 |
51
+ | `get_service_overview` | 특정 서비스의 트랜잭션 성능과 에러 요약 조회 |
52
+ | `get_transactions` | 특정 서비스의 트랜잭션 성능 통계 조회 |
53
+ | `get_errors` | 특정 서비스의 에러 그룹 조회 |
54
+
55
+ ## 사용 예시
56
+
57
+ MCP 설정 후 Claude에게 자연어로 요청하면 됩니다.
58
+
59
+ - "APM 서비스 목록 보여줘"
60
+ - "auth-service의 최근 1시간 에러 조회해줘"
61
+ - "payment-service 트랜잭션 성능 확인해줘"
62
+
63
+ ## License
64
+
65
+ ISC
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { KibanaClient } from "./kibana-client.js";
5
+ import { registerServiceTools } from "./tools/services.js";
6
+ import { registerErrorTools } from "./tools/errors.js";
7
+ import { registerTransactionTools } from "./tools/transactions.js";
8
+ const kibanaUrl = process.env.KIBANA_URL;
9
+ const username = process.env.KIBANA_USERNAME;
10
+ const password = process.env.KIBANA_PASSWORD;
11
+ if (!kibanaUrl || !username || !password) {
12
+ console.error("KIBANA_URL, KIBANA_USERNAME, KIBANA_PASSWORD environment variables are required");
13
+ process.exit(1);
14
+ }
15
+ const client = new KibanaClient(kibanaUrl, username, password);
16
+ const server = new McpServer({
17
+ name: "elastic-apm-mcp-server",
18
+ version: "1.0.0",
19
+ });
20
+ registerServiceTools(server, client);
21
+ registerErrorTools(server, client);
22
+ registerTransactionTools(server, client);
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
@@ -0,0 +1,89 @@
1
+ export class KibanaClient {
2
+ baseUrl;
3
+ headers;
4
+ constructor(kibanaUrl, username, password) {
5
+ this.baseUrl = kibanaUrl.replace(/\/$/, "");
6
+ const credentials = Buffer.from(`${username}:${password}`).toString("base64");
7
+ this.headers = {
8
+ Authorization: `Basic ${credentials}`,
9
+ "kbn-xsrf": "true",
10
+ "x-elastic-internal-origin": "kibana",
11
+ "Content-Type": "application/json",
12
+ };
13
+ }
14
+ async request(path, params) {
15
+ const url = new URL(`${this.baseUrl}${path}`);
16
+ if (params) {
17
+ for (const [key, value] of Object.entries(params)) {
18
+ url.searchParams.set(key, value);
19
+ }
20
+ }
21
+ const res = await fetch(url.toString(), { headers: this.headers });
22
+ if (!res.ok) {
23
+ const body = await res.text();
24
+ throw new Error(`Kibana API error ${res.status}: ${body}`);
25
+ }
26
+ return res.json();
27
+ }
28
+ async getServices(start, end, environment) {
29
+ return this.request("/internal/apm/services", {
30
+ start,
31
+ end,
32
+ environment: environment ?? "ENVIRONMENT_ALL",
33
+ probability: "1",
34
+ documentType: "transactionMetric",
35
+ rollupInterval: "60m",
36
+ kuery: "",
37
+ useDurationSummary: "true",
38
+ });
39
+ }
40
+ async getServiceOverview(serviceName, start, end, environment) {
41
+ const env = environment ?? "ENVIRONMENT_ALL";
42
+ const base = `/internal/apm/services/${encodeURIComponent(serviceName)}`;
43
+ const common = {
44
+ start,
45
+ end,
46
+ environment: env,
47
+ probability: "1",
48
+ documentType: "transactionMetric",
49
+ rollupInterval: "60m",
50
+ kuery: "",
51
+ };
52
+ const [transactions, errors] = await Promise.all([
53
+ this.request(`${base}/transactions/groups/main_statistics`, {
54
+ ...common,
55
+ transactionType: "request",
56
+ latencyAggregationType: "avg",
57
+ useDurationSummary: "true",
58
+ }),
59
+ this.request(`${base}/errors/groups/main_statistics`, {
60
+ start,
61
+ end,
62
+ environment: env,
63
+ kuery: "",
64
+ }),
65
+ ]);
66
+ return { transactions, errors };
67
+ }
68
+ async getErrors(serviceName, start, end, environment) {
69
+ return this.request(`/internal/apm/services/${encodeURIComponent(serviceName)}/errors/groups/main_statistics`, {
70
+ start,
71
+ end,
72
+ environment: environment ?? "ENVIRONMENT_ALL",
73
+ kuery: "",
74
+ });
75
+ }
76
+ async getTransactions(serviceName, start, end, transactionType = "request", environment) {
77
+ return this.request(`/internal/apm/services/${encodeURIComponent(serviceName)}/transactions/groups/main_statistics`, {
78
+ start,
79
+ end,
80
+ transactionType,
81
+ environment: environment ?? "ENVIRONMENT_ALL",
82
+ latencyAggregationType: "avg",
83
+ documentType: "transactionMetric",
84
+ rollupInterval: "60m",
85
+ kuery: "",
86
+ useDurationSummary: "true",
87
+ });
88
+ }
89
+ }
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ export function registerErrorTools(server, client) {
3
+ server.tool("get_errors", "특정 서비스의 에러 그룹을 조회합니다", {
4
+ serviceName: z.string().describe("서비스 이름"),
5
+ start: z.string().describe("조회 시작 시간 (ISO 8601)"),
6
+ end: z.string().describe("조회 종료 시간 (ISO 8601)"),
7
+ environment: z.string().optional().describe("환경 필터"),
8
+ }, async ({ serviceName, start, end, environment }) => {
9
+ const data = await client.getErrors(serviceName, start, end, environment);
10
+ return {
11
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
12
+ };
13
+ });
14
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ export function registerServiceTools(server, client) {
3
+ server.tool("list_services", "APM에 등록된 서비스 목록을 조회합니다", {
4
+ start: z.string().describe("조회 시작 시간 (ISO 8601, e.g. 2026-04-01T00:00:00Z)"),
5
+ end: z.string().describe("조회 종료 시간 (ISO 8601)"),
6
+ environment: z.string().optional().describe("환경 필터 (e.g. production, staging)"),
7
+ }, async ({ start, end, environment }) => {
8
+ const data = await client.getServices(start, end, environment);
9
+ return {
10
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
11
+ };
12
+ });
13
+ server.tool("get_service_overview", "특정 서비스의 트랜잭션 성능과 에러 요약을 조회합니다", {
14
+ serviceName: z.string().describe("서비스 이름"),
15
+ start: z.string().describe("조회 시작 시간 (ISO 8601)"),
16
+ end: z.string().describe("조회 종료 시간 (ISO 8601)"),
17
+ environment: z.string().optional().describe("환경 필터"),
18
+ }, async ({ serviceName, start, end, environment }) => {
19
+ const data = await client.getServiceOverview(serviceName, start, end, environment);
20
+ return {
21
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
22
+ };
23
+ });
24
+ }
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ export function registerTransactionTools(server, client) {
3
+ server.tool("get_transactions", "특정 서비스의 트랜잭션 성능 통계를 조회합니다", {
4
+ serviceName: z.string().describe("서비스 이름"),
5
+ start: z.string().describe("조회 시작 시간 (ISO 8601)"),
6
+ end: z.string().describe("조회 종료 시간 (ISO 8601)"),
7
+ transactionType: z
8
+ .string()
9
+ .default("request")
10
+ .describe("트랜잭션 타입 (e.g. request, messaging)"),
11
+ environment: z.string().optional().describe("환경 필터"),
12
+ }, async ({ serviceName, start, end, transactionType, environment }) => {
13
+ const data = await client.getTransactions(serviceName, start, end, transactionType, environment);
14
+ return {
15
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
16
+ };
17
+ });
18
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "elastic-apm-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for monitoring Elastic APM via Kibana API",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "elastic-apm-mcp-server": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "start": "node dist/index.js",
15
+ "build": "tsc",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "elastic",
21
+ "apm",
22
+ "kibana",
23
+ "monitoring"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/kimjunyoung90/elastic-apm-mcp-server.git"
28
+ },
29
+ "author": "",
30
+ "license": "ISC",
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.29.0",
33
+ "zod": "^4.3.6"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^25.5.0",
37
+ "tsx": "^4.21.0",
38
+ "typescript": "^6.0.2"
39
+ }
40
+ }