@awarecorp/mcp-logger 0.0.2-dev.3 → 0.0.2
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 +134 -227
- package/bin/mcp-logger.js +1 -1
- package/dist/cli/interceptor.js +1 -1
- package/dist/cli/main.js +2 -0
- package/dist/cli/pairing.js +1 -0
- package/dist/core/config.js +1 -0
- package/dist/core/span-builder.js +1 -0
- package/dist/core/span-utils.js +1 -0
- package/dist/core/telemetry.js +1 -0
- package/dist/core/types.d.ts +174 -0
- package/dist/index.d.ts +74 -52
- package/dist/index.js +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/instrumentation.js +1 -1
- package/dist/sdk/types.d.ts +29 -3
- package/package.json +17 -1
- package/dist/cli/cli.js +0 -2
- package/dist/shared/config.js +0 -1
- package/dist/shared/span-utils.js +0 -1
- package/dist/shared/telemetry.js +0 -1
- package/dist/shared/types.d.ts +0 -53
package/README.md
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
# @awarecorp/mcp-logger
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Unified MCP Observability Solution** - Monitor your Model Context Protocol servers with zero configuration.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@awarecorp/mcp-logger)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
[Aware Corp](https://awarecorp.io/)
|
|
4
9
|
|
|
5
10
|
---
|
|
6
11
|
|
|
7
|
-
## 🎯
|
|
12
|
+
## 🎯 Overview
|
|
13
|
+
|
|
14
|
+
`@awarecorp/mcp-logger` provides comprehensive observability for MCP servers in two ways:
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
- **SDK**: Direct integration for custom MCP servers
|
|
17
|
+
- **CLI**: Zero-code wrapper for existing MCP packages
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
이미 만들어진 MCP 서버(npm 패키지)를 사용한다면, CLI를 사용하세요.
|
|
19
|
+
All telemetry data is sent to OpenTelemetry-compatible endpoints with automatic request-response pairing.
|
|
14
20
|
|
|
15
21
|
---
|
|
16
22
|
|
|
17
|
-
## 📦
|
|
23
|
+
## 📦 Installation
|
|
18
24
|
|
|
19
25
|
```bash
|
|
20
26
|
npm install @awarecorp/mcp-logger
|
|
@@ -22,296 +28,197 @@ npm install @awarecorp/mcp-logger
|
|
|
22
28
|
|
|
23
29
|
---
|
|
24
30
|
|
|
25
|
-
##
|
|
31
|
+
## 🚀 Quick Start
|
|
26
32
|
|
|
27
|
-
###
|
|
33
|
+
### SDK Mode
|
|
34
|
+
|
|
35
|
+
Instrument your MCP server with one line of code:
|
|
28
36
|
|
|
29
37
|
```typescript
|
|
30
|
-
import {
|
|
31
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
32
|
-
import { initMCPLogger } from '@awarecorp/mcp-logger';
|
|
33
|
-
|
|
34
|
-
const server = new Server(
|
|
35
|
-
{ name: 'my-server', version: '1.0.0' },
|
|
36
|
-
{ capabilities: { tools: {} } }
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// 🔥 자동 계측 활성화 (이것만 추가하면 됩니다!)
|
|
40
|
-
initMCPLogger(server, {
|
|
41
|
-
apiKey: process.env.AWARE_API_KEY!,
|
|
42
|
-
serviceName: 'my-awesome-mcp-server',
|
|
43
|
-
debug: true, // 디버그 로그 활성화 (선택사항)
|
|
44
|
-
});
|
|
38
|
+
import { trace } from '@awarecorp/mcp-logger';
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
{ name: 'read_file', description: 'Read a file' },
|
|
50
|
-
{ name: 'write_file', description: 'Write to a file' },
|
|
51
|
-
],
|
|
52
|
-
}));
|
|
53
|
-
|
|
54
|
-
server.setRequestHandler('tools/call', async (request) => {
|
|
55
|
-
const { name, arguments: args } = request.params;
|
|
56
|
-
// 이 핸들러는 자동으로 로깅됩니다!
|
|
57
|
-
return { result: `Called ${name}` };
|
|
40
|
+
trace(server, {
|
|
41
|
+
apiKey: 'YOUR_API_KEY',
|
|
42
|
+
serviceName: 'my-mcp-server'
|
|
58
43
|
});
|
|
44
|
+
```
|
|
59
45
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
46
|
+
### CLI Mode
|
|
47
|
+
|
|
48
|
+
Wrap any existing MCP server without code changes:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx @awarecorp/mcp-logger \
|
|
52
|
+
-k YOUR_API_KEY \
|
|
53
|
+
-s my-server \
|
|
54
|
+
npx your-mcp-server
|
|
63
55
|
```
|
|
64
56
|
|
|
65
|
-
|
|
57
|
+
---
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
69
|
-
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
70
|
-
const { initMCPLogger } = require('@awarecorp/mcp-logger');
|
|
59
|
+
## 📋 Features
|
|
71
60
|
|
|
72
|
-
|
|
73
|
-
{ name: 'my-server', version: '1.0.0' },
|
|
74
|
-
{ capabilities: { tools: {} } }
|
|
75
|
-
);
|
|
61
|
+
### Core Capabilities
|
|
76
62
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
- **Automatic Request-Response Pairing**: Single span per request-response cycle
|
|
64
|
+
- **Selective Method Tracking**: Only tracks `tools/call` and `tools/list` for efficiency
|
|
65
|
+
- **Custom Event Logging**: Add context-aware logs within request handlers
|
|
66
|
+
- **Transport Support**: stdio and SSE (Server-Sent Events)
|
|
67
|
+
- **Zero Configuration**: Works out of the box with sensible defaults
|
|
82
68
|
|
|
83
|
-
|
|
84
|
-
tools: [{ name: 'test', description: 'Test tool' }],
|
|
85
|
-
}));
|
|
69
|
+
### Telemetry Data
|
|
86
70
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
All spans include:
|
|
72
|
+
|
|
73
|
+
- Request/response timestamps and payloads
|
|
74
|
+
- Method name and parameters
|
|
75
|
+
- Tool names and arguments (for `tools/call`)
|
|
76
|
+
- Custom events and logs
|
|
77
|
+
- Error details (if any)
|
|
78
|
+
- Duration metrics
|
|
79
|
+
- CLI server information (command, args, env)
|
|
80
|
+
|
|
81
|
+
### Data Format
|
|
82
|
+
|
|
83
|
+
Optimized for Elasticsearch with flat structure:
|
|
84
|
+
- `mcp.method`: Method name
|
|
85
|
+
- `mcp.source`: `sdk` or `cli`
|
|
86
|
+
- `mcp.transport`: `stdio` or `sse`
|
|
87
|
+
- `mcp.duration_ms`: Request duration
|
|
88
|
+
- `mcp.tool.name`: Tool name (tools/call only)
|
|
89
|
+
- `mcp.events`: Custom log entries (JSON array)
|
|
90
|
+
- `mcp.cli.server`: CLI execution info (JSON object)
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 🔧 SDK API
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
### `trace(server, options)`
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
Enable automatic instrumentation on an MCP server.
|
|
96
99
|
|
|
97
100
|
**Parameters:**
|
|
98
|
-
- `server
|
|
99
|
-
- `options
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- `endpoint` (string, 옵션): 커스텀 OTLP 엔드포인트
|
|
101
|
+
- `server`: MCP Server instance
|
|
102
|
+
- `options.apiKey`: API key for authentication (required)
|
|
103
|
+
- `options.serviceName`: Service identifier (optional, auto-generated if omitted)
|
|
104
|
+
- `options.endpoint`: Custom OTLP endpoint (optional)
|
|
105
|
+
- `options.debug`: Enable debug logging (optional, default: false)
|
|
104
106
|
|
|
105
107
|
**Returns:** `void`
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
- `Error`: apiKey가 제공되지 않은 경우
|
|
109
|
-
- `Error`: server 인스턴스가 제공되지 않은 경우
|
|
109
|
+
### `trace.addLog(entry)`
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Add custom log entries within request handlers.
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
**Parameters:**
|
|
114
|
+
- `entry.level`: Log level (`info` | `warn` | `error`)
|
|
115
|
+
- `entry.message`: Log message (required)
|
|
116
|
+
- `entry.metadata`: Additional data (optional)
|
|
117
|
+
- `entry.timestamp`: Unix timestamp in milliseconds (optional, auto-generated)
|
|
114
118
|
|
|
115
|
-
|
|
119
|
+
**Returns:** `void`
|
|
116
120
|
|
|
117
|
-
|
|
118
|
-
npx @awarecorp/mcp-logger \
|
|
119
|
-
--api-key YOUR_API_KEY \
|
|
120
|
-
-- \
|
|
121
|
-
npx @modelcontextprotocol/server-filesystem /path/to/dir
|
|
122
|
-
```
|
|
121
|
+
### `trace.shutdown()`
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
Gracefully shutdown telemetry and flush pending data.
|
|
125
124
|
|
|
126
|
-
|
|
125
|
+
**Returns:** `Promise<void>`
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 🖥️ CLI Usage
|
|
130
|
+
|
|
131
|
+
### Basic Syntax
|
|
131
132
|
|
|
132
|
-
사용:
|
|
133
133
|
```bash
|
|
134
|
-
mcp-logger
|
|
134
|
+
mcp-logger [options] <command...>
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
###
|
|
137
|
+
### Options
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
139
|
+
- `-k, --api-key <key>`: API key (required)
|
|
140
|
+
- `-s, --service-name <name>`: Service name (optional)
|
|
141
|
+
- `-e, --endpoint <url>`: Custom OTLP endpoint (optional)
|
|
142
|
+
|
|
143
|
+
### Examples
|
|
144
|
+
|
|
145
|
+
**Wrap an npx command:**
|
|
146
|
+
```bash
|
|
147
|
+
mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesystem /path
|
|
149
148
|
```
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
**Use with Claude Desktop:**
|
|
152
151
|
```json
|
|
153
152
|
{
|
|
154
153
|
"mcpServers": {
|
|
155
|
-
"
|
|
154
|
+
"my-server": {
|
|
156
155
|
"command": "npx",
|
|
157
156
|
"args": [
|
|
158
157
|
"-y",
|
|
159
158
|
"@awarecorp/mcp-logger",
|
|
160
|
-
"
|
|
161
|
-
"
|
|
159
|
+
"-k",
|
|
160
|
+
"YOUR_API_KEY",
|
|
161
|
+
"-s",
|
|
162
|
+
"my-server",
|
|
162
163
|
"--",
|
|
163
|
-
"
|
|
164
|
-
"
|
|
164
|
+
"npx",
|
|
165
|
+
"-y",
|
|
166
|
+
"your-mcp-server"
|
|
165
167
|
]
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
171
|
```
|
|
170
172
|
|
|
171
|
-
**이게 전부입니다!** 코드 수정 없이 모든 요청/응답이 Aware 플랫폼에 기록됩니다.
|
|
172
|
-
|
|
173
|
-
### CLI 옵션
|
|
174
|
-
|
|
175
|
-
#### 필수 옵션
|
|
176
|
-
- `--api-key <key>` 또는 `-k <key>`: Aware API 키 (필수)
|
|
177
|
-
|
|
178
|
-
#### 선택 옵션
|
|
179
|
-
- `--service-name <name>` 또는 `-s <name>`: 서비스 식별 이름 (기본값: 자동 생성)
|
|
180
|
-
- `--debug` 또는 `-d`: 디버그 로그 활성화
|
|
181
|
-
- `--endpoint <url>` 또는 `-e <url>`: 커스텀 OTLP 엔드포인트
|
|
182
|
-
|
|
183
|
-
### CLI 사용 예시
|
|
184
|
-
|
|
185
|
-
#### 파일시스템 서버
|
|
186
|
-
```bash
|
|
187
|
-
npx @awarecorp/mcp-logger \
|
|
188
|
-
--api-key abc123 \
|
|
189
|
-
--service-name my-filesystem \
|
|
190
|
-
-- \
|
|
191
|
-
npx -y @modelcontextprotocol/server-filesystem /Users/username/Documents
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
#### GitHub 서버
|
|
195
|
-
```bash
|
|
196
|
-
npx @awarecorp/mcp-logger \
|
|
197
|
-
-k abc123 \
|
|
198
|
-
-s github-integration \
|
|
199
|
-
-- \
|
|
200
|
-
npx -y @modelcontextprotocol/server-github
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
#### Brave Search 서버 (디버그 모드)
|
|
204
|
-
```bash
|
|
205
|
-
npx @awarecorp/mcp-logger \
|
|
206
|
-
--api-key abc123 \
|
|
207
|
-
--service-name brave-search \
|
|
208
|
-
--debug \
|
|
209
|
-
-- \
|
|
210
|
-
npx -y @modelcontextprotocol/server-brave-search
|
|
211
|
-
```
|
|
212
|
-
|
|
213
173
|
---
|
|
214
174
|
|
|
215
|
-
## 🏗️
|
|
175
|
+
## 🏗️ How It Works
|
|
216
176
|
|
|
217
|
-
|
|
177
|
+
### Request-Response Pairing
|
|
218
178
|
|
|
219
|
-
|
|
220
|
-
@awarecorp/mcp-logger
|
|
221
|
-
├── SDK (공개 API)
|
|
222
|
-
│ ├── initMCPLogger() - MCP 서버 계측
|
|
223
|
-
│ └── shutdown() - 텔레메트리 종료
|
|
224
|
-
│
|
|
225
|
-
├── CLI (바이너리만 접근 가능)
|
|
226
|
-
│ └── mcp-logger - CLI 래퍼
|
|
227
|
-
│
|
|
228
|
-
└── Shared (내부 구현)
|
|
229
|
-
├── telemetry - OpenTelemetry 초기화
|
|
230
|
-
├── span-utils - Span 유틸리티
|
|
231
|
-
└── config - 공통 설정
|
|
232
|
-
```
|
|
179
|
+
Creates **one span per request-response pair** for complete transaction context:
|
|
233
180
|
|
|
234
|
-
|
|
181
|
+
1. Request arrives → stored in pending map
|
|
182
|
+
2. Response arrives → matched by ID, single span created
|
|
183
|
+
3. Timeout (30s) → pending requests cleared
|
|
235
184
|
|
|
236
|
-
|
|
237
|
-
2. **CLI는 바이너리로만 접근**: `npx` 또는 `mcp-logger` 명령어로만 사용
|
|
238
|
-
3. **공통 코드 재사용**: OpenTelemetry 로직은 shared 레이어에서 통합 관리
|
|
239
|
-
4. **타입 안전성**: CLI는 타입 정의가 노출되지 않음
|
|
185
|
+
### Tracked Methods
|
|
240
186
|
|
|
241
|
-
|
|
187
|
+
Only tracks business-critical methods:
|
|
188
|
+
- `tools/call` - Tool executions
|
|
189
|
+
- `tools/list` - Tool discovery
|
|
242
190
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
### 빌드
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
npm run build
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### 로컬 테스트
|
|
252
|
-
|
|
253
|
-
#### SDK 테스트
|
|
254
|
-
```typescript
|
|
255
|
-
// test.ts
|
|
256
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
257
|
-
import { initMCPLogger } from './src/index.js';
|
|
258
|
-
|
|
259
|
-
const server = new Server({ name: 'test', version: '1.0.0' }, { capabilities: {} });
|
|
260
|
-
initMCPLogger(server, { apiKey: 'test', debug: true });
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
#### CLI 테스트
|
|
264
|
-
```bash
|
|
265
|
-
npm run build
|
|
266
|
-
node bin/mcp-logger.js --help
|
|
267
|
-
```
|
|
191
|
+
Protocol methods (`initialize`, `ping`, etc.) are ignored.
|
|
268
192
|
|
|
269
193
|
---
|
|
270
194
|
|
|
271
|
-
##
|
|
195
|
+
## 📊 Telemetry Data Structure
|
|
272
196
|
|
|
273
|
-
|
|
197
|
+
Each span includes:
|
|
198
|
+
- Request/response timestamps and payloads
|
|
199
|
+
- Method name and parameters
|
|
200
|
+
- Tool names and arguments (for `tools/call`)
|
|
201
|
+
- Custom events and logs
|
|
202
|
+
- Error details and duration metrics
|
|
203
|
+
- CLI server information (command, args, env)
|
|
274
204
|
|
|
275
205
|
---
|
|
276
206
|
|
|
277
|
-
## 🤝
|
|
207
|
+
## 🤝 Contributing
|
|
278
208
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
자세한 개발 가이드는 [DEVELOPMENT.md](./DEVELOPMENT.md)를 참조하세요.
|
|
209
|
+
For development setup and guidelines, visit [GitHub Repository](https://github.com/awarecorp/mcp-logger).
|
|
282
210
|
|
|
283
211
|
---
|
|
284
212
|
|
|
285
|
-
##
|
|
286
|
-
|
|
287
|
-
**권장:** GitHub Actions를 사용한 자동 배포
|
|
288
|
-
|
|
289
|
-
### 자동 배포 (GitHub Actions) ⭐
|
|
290
|
-
```bash
|
|
291
|
-
# Dev 버전 (develop 브랜치)
|
|
292
|
-
git checkout develop
|
|
293
|
-
npm run version:dev
|
|
294
|
-
git push origin develop --tags
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
자세한 가이드:
|
|
298
|
-
- [GITHUB_SETUP.md](./GITHUB_SETUP.md) - Repository 초기 설정
|
|
299
|
-
- [GITHUB_ACTIONS.md](./GITHUB_ACTIONS.md) - 자동 배포 사용법
|
|
300
|
-
|
|
301
|
-
### 수동 배포 (로컬)
|
|
302
|
-
```bash
|
|
303
|
-
# Dev 버전
|
|
304
|
-
npm run version:dev
|
|
305
|
-
npm run publish:dev
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
**참고:** Stable 버전은 아직 지원하지 않습니다.
|
|
213
|
+
## 📄 License
|
|
309
214
|
|
|
310
|
-
|
|
215
|
+
MIT © [Aware Corp](https://awarecorp.io/)
|
|
311
216
|
|
|
312
217
|
---
|
|
313
218
|
|
|
314
|
-
##
|
|
219
|
+
## 🔗 Links
|
|
315
220
|
|
|
316
|
-
-
|
|
317
|
-
-
|
|
221
|
+
- [Aware Corp](https://awarecorp.io/)
|
|
222
|
+
- [npm Package](https://www.npmjs.com/package/@awarecorp/mcp-logger)
|
|
223
|
+
- [GitHub Repository](https://github.com/awarecorp/mcp-logger)
|
|
224
|
+
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
package/bin/mcp-logger.js
CHANGED
package/dist/cli/interceptor.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Transform as e}from"stream";
|
|
1
|
+
import{Transform as e}from"stream";class s extends e{buffer="";direction;pairer;debug;constructor(e,s,r=!1){super(),this.direction=e,this.pairer=s,this.debug=r}_transform(e,s,r){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),r()}catch(e){r(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);this.handleMessage(s)}catch(s){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){"request"===this.direction&&e.method?this.pairer.onRequest(e):"response"!==this.direction||void 0===e.id||e.method||this.pairer.onResponse(e)}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.pairer.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(s){e(s)}}}export{s as MessageInterceptor};
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
function e(){return h}function r(){return I}async function t(){if(d)try{await d.shutdown(),d=null,h=null,f=!1,I=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,r=1e4){try{const t=JSON.stringify(e);return t.length>r?t.slice(0,r)+"... (truncated)":t}catch(e){return null}}function o(t){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+t.method;o.startActiveSpan(n,e=>{try{const o={"mcp.method":t.method,"mcp.source":t.source,"mcp.transport":t.transport,"mcp.log_type":"request-response","mcp.request.id":t.request.id+"","mcp.request.timestamp":t.request.timestamp,"mcp.request.params":s(t.request.params)||"{}","mcp.request.params.size":JSON.stringify(t.request.params||{}).length,"mcp.response.timestamp":t.response.timestamp,"mcp.duration_ms":t.duration};if("tools/call"===t.method&&t.request.params){const e=t.request.params.name,r=t.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),r){const e=s(r);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==t.response.result){const e=s(t.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(t.response.result).length)}if(t.request.headers){const e=s(t.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(t.request.headers).length)}if("cli"===t.source){const e=r();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(t.customEvents&&t.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(t.customEvents),o["mcp.events.count"]=t.customEvents.length,t.customEvents.forEach(r=>{e.addEvent("custom."+(r.level||"info"),{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp||Date.now()})})),t.response.error){o["mcp.error"]=!0;const r=t.response.error instanceof Error?t.response.error.message:t.response.error+"";o["mcp.error.message"]=r,t.response.error instanceof Error&&e.recordException(t.response.error),e.setStatus({code:u.ERROR,message:r})}else e.setStatus({code:u.OK});!function(e,r){for(const[t,s]of Object.entries(r))null!=s&&e.setAttribute(t,s)}(e,o)}catch(r){console.error("[MCP Logger] Error creating paired span:",r),e.setStatus({code:u.ERROR,message:r+""})}finally{e.end()}})}import{spawn as n}from"child_process";import{program as i}from"commander";import{NodeSDK as c}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as a}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as p}from"@opentelemetry/resources";import{trace as m,SpanStatusCode as u}from"@opentelemetry/api";import{Transform as l}from"stream";const g={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let d=null,h=null,f=!1,I=null;class y extends l{buffer="";direction;pairer;debug;constructor(e,r,t=!1){super(),this.direction=e,this.pairer=r,this.debug=t}_transform(e,r,t){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),t()}catch(e){t(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);this.handleMessage(r)}catch(r){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){"request"===this.direction&&e.method?this.pairer.onRequest(e):"response"!==this.direction||void 0===e.id||e.method||this.pairer.onResponse(e)}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.pairer.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(r){e(r)}}}class S{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(e){var r;r=e.method,g.TRACKED_METHODS.includes(r)?e.id?(this.pendingRequests.set(e.id,{request:e,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${e.method} (ID: ${e.id})`),setTimeout(()=>this.cleanupStale(e.id),this.TIMEOUT)):this.debug&&console.error("[mcp-logger CLI] Request without ID, skipping:",e.method):this.debug&&console.error("[mcp-logger CLI] Skipping method: "+e.method)}onResponse(e){if(!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const r=this.pendingRequests.get(e.id);if(!r)return void(this.debug&&console.error("[mcp-logger CLI] No matching request for response ID: "+e.id));const t=Date.now(),s=t-r.timestamp;this.debug&&console.error(`[mcp-logger CLI] Pairing success: ${r.request.method} (${s}ms)`),this.createPairedSpan(r.request,e,r.timestamp,t,s),this.pendingRequests.delete(e.id)}createPairedSpan(e,r,t,s,n){o({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:t,params:e.params},response:{timestamp:s,result:r.result,error:r.error},duration:n})}cleanupStale(e){const r=this.pendingRequests.get(e);r&&Date.now()-r.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] Request timeout: ${r.request.method} (ID: ${e})`),this.pendingRequests.delete(e))}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}i.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-s, --service-name <name>","Service name for identification").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(e,r)=>{try{const s=!0;!function(e){if(f)return console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),h;const r=e.serviceName||`${g.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||g.ENDPOINT)),console.error("[MCP Logger] Service: "+r),d=new c({resource:new p({"service.name":r,"service.version":g.SDK_VERSION}),traceExporter:new a({url:e.endpoint||g.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),d.start(),h=m.getTracer("mcp-logger",g.SDK_VERSION),f=!0,console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{console.error("[MCP Logger] Shutting down telemetry..."),await t()};process.once("SIGTERM",s),process.once("SIGINT",s)}({...r,debug:s}),console.error("[MCP Logger CLI] Starting MCP server:",e.join(" "));const[o,...i]=e;!function(e,r,t){I={command:e,args:r,env:t}}(o,i,{...process.env});const u=n(o,i,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),l=new S(s),C=new y("request",l,s);process.stdin.pipe(C).pipe(u.stdin);const E=new y("response",l,s);u.stdout.pipe(E).pipe(process.stdout),u.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),l.clear(),await t(),process.exit(1)}),u.on("exit",async(e,r)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),l.clear(),await t(),process.exit(e||0)});let q=!1;const v=async e=>{q||(q=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),u.kill(e),await Promise.race([new Promise(e=>u.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await t(),process.exit(0))};process.on("SIGTERM",()=>v("SIGTERM")),process.on("SIGINT",()=>v("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await t(),process.exit(1)}}),i.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const e={TRACKED_METHODS:["tools/call","tools/list"]};class t{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(t){var s;s=t.method,e.TRACKED_METHODS.includes(s)?t.id?(this.pendingRequests.set(t.id,{request:t,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${t.method} (ID: ${t.id})`),setTimeout(()=>this.cleanupStale(t.id),this.TIMEOUT)):this.debug&&console.error("[mcp-logger CLI] Request without ID, skipping:",t.method):this.debug&&console.error("[mcp-logger CLI] Skipping method: "+t.method)}onResponse(e){if(!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const t=this.pendingRequests.get(e.id);if(!t)return void(this.debug&&console.error("[mcp-logger CLI] No matching request for response ID: "+e.id));const s=Date.now(),o=s-t.timestamp;this.debug&&console.error(`[mcp-logger CLI] Pairing success: ${t.request.method} (${o}ms)`),this.createPairedSpan(t.request,e,t.timestamp,s,o),this.pendingRequests.delete(e.id)}createPairedSpan(e,t,s,o,r){e.method,e.id,e.params,t.result,t.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}cleanupStale(e){const t=this.pendingRequests.get(e);t&&Date.now()-t.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] Request timeout: ${t.request.method} (ID: ${e})`),this.pendingRequests.delete(e))}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}export{t as RequestResponsePairer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(t){return E.TRACKED_METHODS.includes(t)}const E={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};export{E as CONFIG,t as shouldTrackMethod};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";export{e as createPairedSpan};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(t,r){for(const[e,n]of Object.entries(r))null!=n&&t.setAttribute(e,n)}function r(t,r,e,n){try{e?t.addEvent(r,e):t.addEvent(r)}catch(t){n&&console.error("[MCP Logger] Failed to add span event:",t)}}function e(t,r,e){const n=r instanceof Error?r.message:"Unknown error",o=r instanceof Error?r.constructor.name:"Error";r instanceof Error&&t.recordException(r),t.setStatus({code:s.ERROR,message:n}),t.setAttribute("error",!0),t.setAttribute("error.type",o),t.setAttribute("error.message",n),e&&console.error("[MCP Logger] Error recorded in span:",r)}function n(t){t.setStatus({code:s.OK})}function o(t,r=1e4){try{const e=JSON.stringify(t);return e.length>r?e.slice(0,r)+"... (truncated)":e}catch(t){return null}}function c(){const t={},r=a.active(),e=i.getSpan(r);if(e){const r=e.spanContext();r&&(t.traceparent=`00-${r.traceId}-${r.spanId}-01`)}return t}import{SpanStatusCode as s,context as a,trace as i}from"@opentelemetry/api";export{r as addSpanEvent,c as getTraceHeaders,n as markSpanSuccess,e as recordSpanError,o as safeStringify,t as setSpanAttributes};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){if(y)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),d;const r=e.serviceName||`${m}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||u)),console.error("[MCP Logger] Service: "+r)),p=new c({resource:new s({"service.name":r,"service.version":g}),traceExporter:new l({url:e.endpoint||u,headers:{"x-api-key":e.apiKey}})}),p.start(),d=a.getTracer("mcp-logger",g),y=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await i()};return process.once("SIGTERM",o),process.once("SIGINT",o),d}function r(){return d}function o(){return y}function t(e,r,o){f={command:e,args:r,env:o}}function n(){return f}async function i(){if(p)try{await p.shutdown(),p=null,d=null,y=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}import{NodeSDK as c}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as l}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as s}from"@opentelemetry/resources";import{trace as a}from"@opentelemetry/api";const u="https://aware.mcypher.com/v1/traces",g="1.0.0",m="mcp-server";let p=null,d=null,y=!1,f=null;export{n as getCLIServerInfo,r as getTracer,e as initializeTelemetry,o as isInitializedTelemetry,t as setCLIServerInfo,i as shutdownTelemetry};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 공통 설정 상수
|
|
3
|
+
*/
|
|
4
|
+
declare const CONFIG: {
|
|
5
|
+
/**
|
|
6
|
+
* OTLP Endpoint (하드코딩)
|
|
7
|
+
*/
|
|
8
|
+
readonly ENDPOINT: "https://aware.mcypher.com/v1/traces";
|
|
9
|
+
/**
|
|
10
|
+
* SDK 버전
|
|
11
|
+
*/
|
|
12
|
+
readonly SDK_VERSION: "1.0.0";
|
|
13
|
+
/**
|
|
14
|
+
* 서비스 이름 접두사
|
|
15
|
+
*/
|
|
16
|
+
readonly SERVICE_NAME_PREFIX: "mcp-server";
|
|
17
|
+
/**
|
|
18
|
+
* 추적 대상 메서드
|
|
19
|
+
* CLI와 SDK 모두에서 공통으로 사용
|
|
20
|
+
*/
|
|
21
|
+
readonly TRACKED_METHODS: readonly ["tools/call", "tools/list"];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* 추적 대상 메서드 타입
|
|
25
|
+
*/
|
|
26
|
+
type TrackedMethod = typeof CONFIG.TRACKED_METHODS[number];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 공통 타입 정의
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 텔레메트리 초기화 옵션
|
|
34
|
+
*/
|
|
35
|
+
interface TelemetryOptions {
|
|
36
|
+
/**
|
|
37
|
+
* API Key for authentication (필수)
|
|
38
|
+
*/
|
|
39
|
+
apiKey: string;
|
|
40
|
+
/**
|
|
41
|
+
* 서비스 이름 (옵션)
|
|
42
|
+
* 지정하지 않으면 자동으로 'mcp-server-{random}' 형식으로 생성
|
|
43
|
+
*/
|
|
44
|
+
serviceName?: string;
|
|
45
|
+
/**
|
|
46
|
+
* 커스텀 OTLP 엔드포인트 (옵션)
|
|
47
|
+
* 기본값: CONFIG.ENDPOINT
|
|
48
|
+
*/
|
|
49
|
+
endpoint?: string;
|
|
50
|
+
/**
|
|
51
|
+
* 디버그 로그 활성화 (옵션)
|
|
52
|
+
*/
|
|
53
|
+
debug?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Span 속성 인터페이스
|
|
57
|
+
*/
|
|
58
|
+
interface SpanAttributes {
|
|
59
|
+
[key: string]: string | number | boolean | undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* MCP 메시지 방향
|
|
63
|
+
*/
|
|
64
|
+
type MessageDirection = 'request' | 'response';
|
|
65
|
+
/**
|
|
66
|
+
* 로그 타입 분류
|
|
67
|
+
*/
|
|
68
|
+
type MCPLogType = 'request-response' | 'custom-event' | 'internal-event';
|
|
69
|
+
/**
|
|
70
|
+
* JSON-RPC 메시지 타입
|
|
71
|
+
*/
|
|
72
|
+
interface JSONRPCMessage {
|
|
73
|
+
jsonrpc: '2.0';
|
|
74
|
+
id?: string | number;
|
|
75
|
+
method?: string;
|
|
76
|
+
params?: any;
|
|
77
|
+
result?: any;
|
|
78
|
+
error?: {
|
|
79
|
+
code: number;
|
|
80
|
+
message: string;
|
|
81
|
+
data?: any;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* JSON-RPC Request 타입
|
|
86
|
+
*/
|
|
87
|
+
interface JSONRPCRequest extends JSONRPCMessage {
|
|
88
|
+
method: string;
|
|
89
|
+
params?: any;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* JSON-RPC Response 타입
|
|
93
|
+
*/
|
|
94
|
+
interface JSONRPCResponse extends JSONRPCMessage {
|
|
95
|
+
id: string | number;
|
|
96
|
+
result?: any;
|
|
97
|
+
error?: {
|
|
98
|
+
code: number;
|
|
99
|
+
message: string;
|
|
100
|
+
data?: any;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* MCP Span 데이터 구조 (ES 최적화)
|
|
105
|
+
*/
|
|
106
|
+
interface MCPSpanData {
|
|
107
|
+
'mcp.method': TrackedMethod;
|
|
108
|
+
'mcp.source': 'cli' | 'sdk';
|
|
109
|
+
'mcp.transport': 'stdio' | 'sse';
|
|
110
|
+
'mcp.log_type': MCPLogType;
|
|
111
|
+
'mcp.request.id': string;
|
|
112
|
+
'mcp.request.timestamp': number;
|
|
113
|
+
'mcp.request.params': string;
|
|
114
|
+
'mcp.request.params.size': number;
|
|
115
|
+
'mcp.response.timestamp': number;
|
|
116
|
+
'mcp.response.result'?: string;
|
|
117
|
+
'mcp.response.result.size'?: number;
|
|
118
|
+
'mcp.duration_ms': number;
|
|
119
|
+
'mcp.tool.name'?: string;
|
|
120
|
+
'mcp.tool.arguments'?: string;
|
|
121
|
+
'mcp.tool.arguments.size'?: number;
|
|
122
|
+
'mcp.headers'?: string;
|
|
123
|
+
'mcp.headers.size'?: number;
|
|
124
|
+
'mcp.events'?: string;
|
|
125
|
+
'mcp.events.count'?: number;
|
|
126
|
+
'mcp.cli.server'?: string;
|
|
127
|
+
'mcp.error'?: boolean;
|
|
128
|
+
'mcp.error.code'?: number;
|
|
129
|
+
'mcp.error.message'?: string;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 커스텀 로그 엔트리
|
|
133
|
+
*/
|
|
134
|
+
interface CustomLogEntry {
|
|
135
|
+
/**
|
|
136
|
+
* 로그 레벨
|
|
137
|
+
*/
|
|
138
|
+
level?: 'info' | 'warn' | 'error';
|
|
139
|
+
/**
|
|
140
|
+
* 로그 메시지
|
|
141
|
+
*/
|
|
142
|
+
message: string;
|
|
143
|
+
/**
|
|
144
|
+
* 추가 메타데이터 (자유 형식)
|
|
145
|
+
*/
|
|
146
|
+
metadata?: Record<string, any>;
|
|
147
|
+
/**
|
|
148
|
+
* 타임스탬프 (자동 생성 가능)
|
|
149
|
+
*/
|
|
150
|
+
timestamp?: number;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Paired Span 입력 데이터
|
|
154
|
+
*/
|
|
155
|
+
interface PairedSpanInput {
|
|
156
|
+
method: TrackedMethod;
|
|
157
|
+
source: 'cli' | 'sdk';
|
|
158
|
+
transport: 'stdio' | 'sse';
|
|
159
|
+
request: {
|
|
160
|
+
id: string | number;
|
|
161
|
+
timestamp: number;
|
|
162
|
+
params: any;
|
|
163
|
+
headers?: Record<string, string>;
|
|
164
|
+
};
|
|
165
|
+
response: {
|
|
166
|
+
timestamp: number;
|
|
167
|
+
result?: any;
|
|
168
|
+
error?: any;
|
|
169
|
+
};
|
|
170
|
+
duration: number;
|
|
171
|
+
customEvents?: CustomLogEntry[];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type { CustomLogEntry, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, MCPLogType, MCPSpanData, MessageDirection, PairedSpanInput, SpanAttributes, TelemetryOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* 공통 타입 정의
|
|
5
5
|
*/
|
|
6
|
+
|
|
6
7
|
/**
|
|
7
8
|
* 텔레메트리 초기화 옵션
|
|
8
9
|
*/
|
|
@@ -26,32 +27,45 @@ interface TelemetryOptions {
|
|
|
26
27
|
*/
|
|
27
28
|
debug?: boolean;
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* 커스텀 로그 엔트리
|
|
32
|
+
*/
|
|
33
|
+
interface CustomLogEntry$1 {
|
|
34
|
+
/**
|
|
35
|
+
* 로그 레벨
|
|
36
|
+
*/
|
|
37
|
+
level?: 'info' | 'warn' | 'error';
|
|
38
|
+
/**
|
|
39
|
+
* 로그 메시지
|
|
40
|
+
*/
|
|
41
|
+
message: string;
|
|
42
|
+
/**
|
|
43
|
+
* 추가 메타데이터 (자유 형식)
|
|
44
|
+
*/
|
|
45
|
+
metadata?: Record<string, any>;
|
|
46
|
+
/**
|
|
47
|
+
* 타임스탬프 (자동 생성 가능)
|
|
48
|
+
*/
|
|
49
|
+
timestamp?: number;
|
|
50
|
+
}
|
|
29
51
|
|
|
30
52
|
/**
|
|
31
53
|
* SDK 타입 정의
|
|
32
54
|
*/
|
|
33
55
|
|
|
34
56
|
/**
|
|
35
|
-
*
|
|
57
|
+
* Trace 옵션
|
|
36
58
|
*/
|
|
37
|
-
interface
|
|
59
|
+
interface TraceOptions extends TelemetryOptions {
|
|
38
60
|
}
|
|
39
61
|
/**
|
|
40
|
-
*
|
|
41
|
-
*/
|
|
42
|
-
type MCPServer = Server;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 공통 텔레메트리 레이어
|
|
46
|
-
*
|
|
47
|
-
* OpenTelemetry SDK 초기화 및 관리를 담당합니다.
|
|
48
|
-
* SDK와 CLI 양쪽에서 공통으로 사용됩니다.
|
|
62
|
+
* Custom Log Entry (re-export from core)
|
|
49
63
|
*/
|
|
50
|
-
|
|
64
|
+
type CustomLogEntry = CustomLogEntry$1;
|
|
51
65
|
/**
|
|
52
|
-
*
|
|
66
|
+
* MCP Server 타입 (재export)
|
|
53
67
|
*/
|
|
54
|
-
|
|
68
|
+
type MCPServer = Server;
|
|
55
69
|
|
|
56
70
|
/**
|
|
57
71
|
* MCP Logger SDK
|
|
@@ -60,56 +74,64 @@ declare function shutdownTelemetry(): Promise<void>;
|
|
|
60
74
|
*/
|
|
61
75
|
|
|
62
76
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* 이 함수는 MCP 서버의 모든 요청/응답을 자동으로 로깅하고 추적합니다.
|
|
66
|
-
* OpenTelemetry를 사용하여 traces를 수집하며, 설정된 엔드포인트로 전송합니다.
|
|
67
|
-
*
|
|
68
|
-
* @param server - MCP Server 인스턴스
|
|
69
|
-
* @param options - 초기화 옵션
|
|
70
|
-
*
|
|
71
|
-
* @throws {Error} apiKey가 제공되지 않은 경우
|
|
72
|
-
* @throws {Error} server 인스턴스가 제공되지 않은 경우
|
|
77
|
+
* trace 네임스페이스
|
|
73
78
|
*
|
|
74
79
|
* @example
|
|
75
|
-
* TypeScript 사용 예시:
|
|
76
80
|
* ```typescript
|
|
77
|
-
* import {
|
|
78
|
-
* import { initMCPLogger } from '@awarecorp/mcp-logger';
|
|
79
|
-
*
|
|
80
|
-
* const server = new Server({ name: 'my-server', version: '1.0.0' }, {
|
|
81
|
-
* capabilities: { tools: {} }
|
|
82
|
-
* });
|
|
81
|
+
* import { trace } from '@awarecorp/mcp-logger';
|
|
83
82
|
*
|
|
84
|
-
*
|
|
83
|
+
* // 서버 계측
|
|
84
|
+
* trace(server, {
|
|
85
85
|
* apiKey: 'your-api-key',
|
|
86
|
-
* serviceName: 'my-
|
|
87
|
-
* debug: true,
|
|
86
|
+
* serviceName: 'my-service',
|
|
88
87
|
* });
|
|
89
88
|
*
|
|
89
|
+
* // Handler 내부에서 커스텀 로그 추가
|
|
90
90
|
* server.setRequestHandler('tools/call', async (request) => {
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
91
|
+
* trace.addLog({
|
|
92
|
+
* level: 'info',
|
|
93
|
+
* message: 'Processing started',
|
|
94
|
+
* metadata: { id: request.params.arguments.id },
|
|
95
|
+
* });
|
|
95
96
|
*
|
|
96
|
-
*
|
|
97
|
-
* JavaScript 사용 예시:
|
|
98
|
-
* ```javascript
|
|
99
|
-
* const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
100
|
-
* const { initMCPLogger } = require('@awarecorp/mcp-logger');
|
|
97
|
+
* const result = await process();
|
|
101
98
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
99
|
+
* trace.addLog({
|
|
100
|
+
* level: 'info',
|
|
101
|
+
* message: 'Processing completed',
|
|
102
|
+
* metadata: { duration: 100 },
|
|
103
|
+
* });
|
|
105
104
|
*
|
|
106
|
-
*
|
|
107
|
-
* apiKey: process.env.API_KEY,
|
|
108
|
-
* serviceName: 'my-mcp-server',
|
|
105
|
+
* return result;
|
|
109
106
|
* });
|
|
110
107
|
* ```
|
|
111
108
|
*/
|
|
112
|
-
declare
|
|
109
|
+
declare const trace: ((server: MCPServer, options: TraceOptions) => void) & {
|
|
110
|
+
/**
|
|
111
|
+
* 현재 active span에 커스텀 로그 추가
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ 주의: Request handler 내부에서만 호출해야 합니다.
|
|
114
|
+
*
|
|
115
|
+
* @param entry - 커스텀 로그 엔트리
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* trace.addLog({
|
|
120
|
+
* level: 'info',
|
|
121
|
+
* message: 'Database query executed',
|
|
122
|
+
* metadata: { query: 'SELECT * FROM users', rows: 42 },
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
addLog(entry: CustomLogEntry$1): void;
|
|
127
|
+
/**
|
|
128
|
+
* Telemetry 종료
|
|
129
|
+
*
|
|
130
|
+
* 일반적으로 명시적으로 호출할 필요는 없습니다.
|
|
131
|
+
* 프로세스 종료 시 자동으로 처리됩니다.
|
|
132
|
+
*/
|
|
133
|
+
shutdown(): Promise<void>;
|
|
134
|
+
};
|
|
113
135
|
|
|
114
|
-
export {
|
|
115
|
-
export type { MCPServer,
|
|
136
|
+
export { trace };
|
|
137
|
+
export type { CustomLogEntry, MCPServer, TraceOptions };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
function e(){return g}function t(){return f}async function r(){if(u)try{await u.shutdown(),u=null,g=null,d=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(r){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+r.method;o.startActiveSpan(n,e=>{try{const o={"mcp.method":r.method,"mcp.source":r.source,"mcp.transport":r.transport,"mcp.log_type":"request-response","mcp.request.id":r.request.id+"","mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),t){const e=s(t);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(r.customEvents&&r.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(r.customEvents),o["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){o["mcp.error"]=!0;const t=r.response.error instanceof Error?r.response.error.message:r.response.error+"";o["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,o)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as n,SpanStatusCode as a,context as c}from"@opentelemetry/api";import{NodeSDK as i}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";const p={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let u=null,g=null,d=!1,f=null;const h=Symbol("mcp-logger.customEvents"),E=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(d)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),g;const t=e.serviceName||`${p.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||p.ENDPOINT)),console.error("[MCP Logger] Service: "+t)),u=new i({resource:new l({"service.name":t,"service.version":p.SDK_VERSION}),traceExporter:new m({url:e.endpoint||p.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),u.start(),g=n.getTracer("mcp-logger",p.SDK_VERSION),d=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",s),process.once("SIGINT",s)}(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const n=e.shape?.method,a=n?._def?.value||"unknown";return function(e){return p.TRACKED_METHODS.includes(e)}(a)?(t.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),r(e,async(e,r)=>{const n=Date.now(),i=`${a}-${n}-${Math.random().toString(36).slice(2,8)}`,m=[],l=c.active().setValue(h,m);try{const p=await c.with(l,async()=>await s(e,r)),u=Date.now();return o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:u,result:p},duration:u-n,customEvents:m.length>0?m:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${u-n}ms):`,{method:a,customEvents:m.length}),p}catch(r){const s=Date.now();throw o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:m.length>0?m:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})):(t.debug&&console.log("[mcp-logger] Skipping instrumentation for method: "+a),r(e,s))},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=c.active().getValue(h);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=n.getSpan(c.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{E as trace};
|
package/dist/sdk/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
function e(){return g}function t(){return f}async function r(){if(u)try{await u.shutdown(),u=null,g=null,d=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(r){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+r.method;o.startActiveSpan(n,e=>{try{const o={"mcp.method":r.method,"mcp.source":r.source,"mcp.transport":r.transport,"mcp.log_type":"request-response","mcp.request.id":r.request.id+"","mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),t){const e=s(t);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(r.customEvents&&r.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(r.customEvents),o["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){o["mcp.error"]=!0;const t=r.response.error instanceof Error?r.response.error.message:r.response.error+"";o["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,o)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as n,SpanStatusCode as a,context as c}from"@opentelemetry/api";import{NodeSDK as i}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";const p={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let u=null,g=null,d=!1,f=null;const h=Symbol("mcp-logger.customEvents"),E=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(d)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),g;const t=e.serviceName||`${p.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||p.ENDPOINT)),console.error("[MCP Logger] Service: "+t)),u=new i({resource:new l({"service.name":t,"service.version":p.SDK_VERSION}),traceExporter:new m({url:e.endpoint||p.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),u.start(),g=n.getTracer("mcp-logger",p.SDK_VERSION),d=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",s),process.once("SIGINT",s)}(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const n=e.shape?.method,a=n?._def?.value||"unknown";return function(e){return p.TRACKED_METHODS.includes(e)}(a)?(t.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),r(e,async(e,r)=>{const n=Date.now(),i=`${a}-${n}-${Math.random().toString(36).slice(2,8)}`,m=[],l=c.active().setValue(h,m);try{const p=await c.with(l,async()=>await s(e,r)),u=Date.now();return o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:u,result:p},duration:u-n,customEvents:m.length>0?m:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${u-n}ms):`,{method:a,customEvents:m.length}),p}catch(r){const s=Date.now();throw o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:m.length>0?m:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})):(t.debug&&console.log("[mcp-logger] Skipping instrumentation for method: "+a),r(e,s))},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=c.active().getValue(h);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=n.getSpan(c.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{E as trace};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";
|
|
1
|
+
function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function t(t,o){const l=t.setRequestHandler.bind(t);t.setRequestHandler=function(t,s){const a=t.shape?.method,i=a?._def?.value||"unknown";return function(e){return r.TRACKED_METHODS.includes(e)}(i)?(o.debug&&console.log("[mcp-logger] Instrumenting handler: "+i),l(t,async(t,r)=>{const l=Date.now(),a=(Math.random().toString(36).slice(2,8),[]),m=n.active().setValue(c,a);try{const c=await n.with(m,async()=>await s(t,r)),u=Date.now();return e(t.params),o.debug&&console.log(`[mcp-logger] Request completed (${u-l}ms):`,{method:i,customEvents:a.length}),c}catch(n){throw e(t.params),o.debug&&console.error("[mcp-logger] Request failed:",n),n}})):(o.debug&&console.log("[mcp-logger] Skipping instrumentation for method: "+i),l(t,s))},o.debug&&console.log("[mcp-logger] Server instrumentation complete")}function o(){return n.active().getValue(c)}import{context as n}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const r={TRACKED_METHODS:["tools/call","tools/list"]},c=Symbol("mcp-logger.customEvents");export{o as getCustomEventsFromContext,t as instrumentMCPServer};
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Request, Result } from '@modelcontextprotocol/sdk/types.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* 공통 타입 정의
|
|
7
7
|
*/
|
|
8
|
+
|
|
8
9
|
/**
|
|
9
10
|
* 텔레메트리 초기화 옵션
|
|
10
11
|
*/
|
|
@@ -28,16 +29,41 @@ interface TelemetryOptions {
|
|
|
28
29
|
*/
|
|
29
30
|
debug?: boolean;
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* 커스텀 로그 엔트리
|
|
34
|
+
*/
|
|
35
|
+
interface CustomLogEntry$1 {
|
|
36
|
+
/**
|
|
37
|
+
* 로그 레벨
|
|
38
|
+
*/
|
|
39
|
+
level?: 'info' | 'warn' | 'error';
|
|
40
|
+
/**
|
|
41
|
+
* 로그 메시지
|
|
42
|
+
*/
|
|
43
|
+
message: string;
|
|
44
|
+
/**
|
|
45
|
+
* 추가 메타데이터 (자유 형식)
|
|
46
|
+
*/
|
|
47
|
+
metadata?: Record<string, any>;
|
|
48
|
+
/**
|
|
49
|
+
* 타임스탬프 (자동 생성 가능)
|
|
50
|
+
*/
|
|
51
|
+
timestamp?: number;
|
|
52
|
+
}
|
|
31
53
|
|
|
32
54
|
/**
|
|
33
55
|
* SDK 타입 정의
|
|
34
56
|
*/
|
|
35
57
|
|
|
36
58
|
/**
|
|
37
|
-
*
|
|
59
|
+
* Trace 옵션
|
|
38
60
|
*/
|
|
39
|
-
interface
|
|
61
|
+
interface TraceOptions extends TelemetryOptions {
|
|
40
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Custom Log Entry (re-export from core)
|
|
65
|
+
*/
|
|
66
|
+
type CustomLogEntry = CustomLogEntry$1;
|
|
41
67
|
/**
|
|
42
68
|
* MCP JSON-RPC Request 타입
|
|
43
69
|
*/
|
|
@@ -63,4 +89,4 @@ interface RequestHandlerExtra {
|
|
|
63
89
|
*/
|
|
64
90
|
type MCPServer = Server;
|
|
65
91
|
|
|
66
|
-
export type { MCPRequest, MCPRequestSchema, MCPResult, MCPServer, RequestHandlerExtra,
|
|
92
|
+
export type { CustomLogEntry, MCPRequest, MCPRequestSchema, MCPResult, MCPServer, RequestHandlerExtra, TraceOptions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awarecorp/mcp-logger",
|
|
3
|
-
"version": "0.0.2
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Unified MCP observability solution - SDK for code integration and CLI for zero-config wrapping",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"prepublishOnly": "npm run build",
|
|
20
20
|
"clean": "rm -rf dist",
|
|
21
21
|
"dev": "tsc --watch",
|
|
22
|
+
"test": "mocha",
|
|
23
|
+
"test:watch": "mocha --watch",
|
|
24
|
+
"test:coverage": "c8 mocha",
|
|
22
25
|
"test:cli": "npm run build:readable && node bin/mcp-logger.js --help",
|
|
23
26
|
"publish:stable": "npm run build && npm publish --access public",
|
|
24
27
|
"publish:dev": "npm run build && npm publish --access public --tag dev",
|
|
@@ -40,6 +43,14 @@
|
|
|
40
43
|
],
|
|
41
44
|
"author": "Aware",
|
|
42
45
|
"license": "MIT",
|
|
46
|
+
"homepage": "https://github.com/awarecorp/mcp-logger#readme",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/awarecorp/mcp-logger.git"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/awarecorp/mcp-logger/issues"
|
|
53
|
+
},
|
|
43
54
|
"peerDependencies": {
|
|
44
55
|
"@modelcontextprotocol/sdk": "^0.5.0"
|
|
45
56
|
},
|
|
@@ -60,10 +71,15 @@
|
|
|
60
71
|
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
61
72
|
"@rollup/plugin-terser": "^0.4.4",
|
|
62
73
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
74
|
+
"@types/chai": "^5.2.3",
|
|
75
|
+
"@types/mocha": "^10.0.10",
|
|
63
76
|
"@types/node": "^20.0.0",
|
|
77
|
+
"chai": "^6.2.0",
|
|
78
|
+
"mocha": "^11.7.4",
|
|
64
79
|
"rollup": "^4.52.5",
|
|
65
80
|
"rollup-plugin-dts": "^6.2.3",
|
|
66
81
|
"tslib": "^2.8.1",
|
|
82
|
+
"tsx": "^4.20.6",
|
|
67
83
|
"typescript": "^5.3.0"
|
|
68
84
|
},
|
|
69
85
|
"engines": {
|
package/dist/cli/cli.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import{spawn as e}from"child_process";import{program as r}from"commander";import{NodeSDK as t}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as o}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as s}from"@opentelemetry/resources";import{trace as i,SpanStatusCode as n}from"@opentelemetry/api";import{Transform as c}from"stream";const a="https://aware.mcypher.com/v1/traces",p="1.0.0";let g=null,d=null,m=!1;function l(){return d}async function u(){if(g)try{await g.shutdown(),g=null,d=null,m=!1}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function h(e,r=1e4){try{const t=JSON.stringify(e);return t.length>r?t.slice(0,r)+"... (truncated)":t}catch(e){return null}}class f extends c{buffer="";direction;debug;constructor(e,r=!1){super(),this.direction=e,this.debug=r}_transform(e,r,t){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[MCP Logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),t()}catch(e){t(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);this.traceMessage(r).catch(e=>{this.debug&&console.error("[MCP Logger CLI] Trace error:",e)})}catch(r){this.debug&&console.error("[MCP Logger CLI] Failed to parse:",e)}}}async traceMessage(e){const r=l();if(!r)return void(this.debug&&console.error("[MCP Logger CLI] Tracer not initialized"));const t=e.method?`mcp.${e.method}`:void 0!==e.id?"mcp.response":"mcp.notification";return new Promise(o=>{r.startActiveSpan(t,r=>{try{if(function(e,r){for(const[t,o]of Object.entries(r))null!=o&&e.setAttribute(t,o)}(r,{"mcp.direction":this.direction,"mcp.jsonrpc":e.jsonrpc||"2.0"}),void 0!==e.id&&r.setAttribute("mcp.id",String(e.id)),e.method&&r.setAttribute("mcp.method",e.method),e.params){const t=h(e.params);t&&(r.setAttribute("mcp.params.size",t.length),t.length<1e3&&r.setAttribute("mcp.params",t))}const t=h(e);t&&function(e,r,t,o){try{t?e.addEvent(r,t):e.addEvent(r)}catch(e){o&&console.error("[MCP Logger] Failed to add span event:",e)}}(r,`mcp.${this.direction}`,{message:t,timestamp:Date.now()},this.debug),this.debug&&console.error(`[MCP Logger CLI] ${this.direction}:`,JSON.stringify(e,null,2)),e.error?(r.setAttribute("mcp.error",!0),r.setAttribute("mcp.error.code",e.error.code),r.setAttribute("mcp.error.message",e.error.message),r.setStatus({code:n.ERROR,message:e.error.message})):function(e){e.setStatus({code:n.OK})}(r)}catch(e){this.debug&&console.error("[MCP Logger CLI] Error in span:",e),r.setStatus({code:n.ERROR,message:String(e)})}finally{r.end(),o()}})})}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.traceMessage(e).catch(e=>{this.debug&&console.error("[MCP Logger CLI] Trace error:",e)})}catch(e){this.debug&&console.error("[MCP Logger CLI] Failed to parse final buffer:",e)}e()}catch(r){e(r)}}}r.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-s, --service-name <name>","Service name for identification").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(r,n)=>{try{const c=!0;!function(e){if(m)return console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),d;const r=e.serviceName||`mcp-server-${Math.random().toString(36).slice(2,8)}`;console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${e.endpoint||a}`),console.error(`[MCP Logger] Service: ${r}`),g=new t({resource:new s({"service.name":r,"service.version":p}),traceExporter:new o({url:e.endpoint||a,headers:{"x-api-key":e.apiKey}})}),g.start(),d=i.getTracer("mcp-logger",p),m=!0,console.error("[MCP Logger] Telemetry initialized successfully");const n=async()=>{console.error("[MCP Logger] Shutting down telemetry..."),await u()};process.once("SIGTERM",n),process.once("SIGINT",n)}({...n,debug:c}),console.error("[MCP Logger CLI] Starting MCP server:",r.join(" "));const[l,...h]=r,C=e(l,h,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),y=new f("request",c);process.stdin.pipe(y).pipe(C.stdin);const L=new f("response",c);C.stdout.pipe(L).pipe(process.stdout),C.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),await u(),process.exit(1)}),C.on("exit",async(e,r)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),await u(),process.exit(e||0)});let M=!1;const P=async e=>{M||(M=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),C.kill(e),await Promise.race([new Promise(e=>C.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await u(),process.exit(0))};process.on("SIGTERM",()=>P("SIGTERM")),process.on("SIGINT",()=>P("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await u(),process.exit(1)}}),r.parse();
|
package/dist/shared/config.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server"};export{e as CONFIG};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{SpanStatusCode as t,context as r,trace as e}from"@opentelemetry/api";function n(t,r){for(const[e,n]of Object.entries(r))null!=n&&t.setAttribute(e,n)}function o(t,r,e,n){try{e?t.addEvent(r,e):t.addEvent(r)}catch(t){n&&console.error("[MCP Logger] Failed to add span event:",t)}}function c(r,e,n){const o=e instanceof Error?e.message:"Unknown error",c=e instanceof Error?e.constructor.name:"Error";e instanceof Error&&r.recordException(e),r.setStatus({code:t.ERROR,message:o}),r.setAttribute("error",!0),r.setAttribute("error.type",c),r.setAttribute("error.message",o),n&&console.error("[MCP Logger] Error recorded in span:",e)}function s(r){r.setStatus({code:t.OK})}function a(t,r=1e4){try{const e=JSON.stringify(t);return e.length>r?e.slice(0,r)+"... (truncated)":e}catch(t){return null}}function i(){const t={},n=r.active(),o=e.getSpan(n);if(o){const r=o.spanContext();r&&(t.traceparent=`00-${r.traceId}-${r.spanId}-01`)}return t}export{o as addSpanEvent,i as getTraceHeaders,s as markSpanSuccess,c as recordSpanError,a as safeStringify,n as setSpanAttributes};
|
package/dist/shared/telemetry.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{NodeSDK as e}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as r}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as o}from"@opentelemetry/resources";import{trace as t}from"@opentelemetry/api";const n="https://aware.mcypher.com/v1/traces",i="1.0.0";let c=null,s=null,l=!1;function a(a){if(l)return a.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),s;const g=a.serviceName||`mcp-server-${Math.random().toString(36).slice(2,8)}`;a.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${a.endpoint||n}`),console.error(`[MCP Logger] Service: ${g}`)),c=new e({resource:new o({"service.name":g,"service.version":i}),traceExporter:new r({url:a.endpoint||n,headers:{"x-api-key":a.apiKey}})}),c.start(),s=t.getTracer("mcp-logger",i),l=!0,a.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const p=async()=>{a.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await u()};return process.once("SIGTERM",p),process.once("SIGINT",p),s}function g(){return s}function p(){return l}async function u(){if(c)try{await c.shutdown(),c=null,s=null,l=!1}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}export{g as getTracer,a as initializeTelemetry,p as isInitializedTelemetry,u as shutdownTelemetry};
|
package/dist/shared/types.d.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 공통 타입 정의
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* 텔레메트리 초기화 옵션
|
|
6
|
-
*/
|
|
7
|
-
interface TelemetryOptions {
|
|
8
|
-
/**
|
|
9
|
-
* API Key for authentication (필수)
|
|
10
|
-
*/
|
|
11
|
-
apiKey: string;
|
|
12
|
-
/**
|
|
13
|
-
* 서비스 이름 (옵션)
|
|
14
|
-
* 지정하지 않으면 자동으로 'mcp-server-{random}' 형식으로 생성
|
|
15
|
-
*/
|
|
16
|
-
serviceName?: string;
|
|
17
|
-
/**
|
|
18
|
-
* 커스텀 OTLP 엔드포인트 (옵션)
|
|
19
|
-
* 기본값: CONFIG.ENDPOINT
|
|
20
|
-
*/
|
|
21
|
-
endpoint?: string;
|
|
22
|
-
/**
|
|
23
|
-
* 디버그 로그 활성화 (옵션)
|
|
24
|
-
*/
|
|
25
|
-
debug?: boolean;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Span 속성 인터페이스
|
|
29
|
-
*/
|
|
30
|
-
interface SpanAttributes {
|
|
31
|
-
[key: string]: string | number | boolean | undefined;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* MCP 메시지 방향
|
|
35
|
-
*/
|
|
36
|
-
type MessageDirection = 'request' | 'response';
|
|
37
|
-
/**
|
|
38
|
-
* JSON-RPC 메시지 타입
|
|
39
|
-
*/
|
|
40
|
-
interface JSONRPCMessage {
|
|
41
|
-
jsonrpc: '2.0';
|
|
42
|
-
id?: string | number;
|
|
43
|
-
method?: string;
|
|
44
|
-
params?: any;
|
|
45
|
-
result?: any;
|
|
46
|
-
error?: {
|
|
47
|
-
code: number;
|
|
48
|
-
message: string;
|
|
49
|
-
data?: any;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type { JSONRPCMessage, MessageDirection, SpanAttributes, TelemetryOptions };
|