@awarecorp/mcp-logger 0.0.2-dev.2 → 0.0.2-dev.5

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 CHANGED
@@ -1,49 +1,51 @@
1
1
  # @awarecorp/mcp-logger
2
2
 
3
- **통합 MCP Observability 솔루션** - 하나의 패키지로 SDK CLI를 모두 제공합니다.
3
+ **Unified MCP Observability Solution** - One package for both SDK and CLI.
4
+
5
+ [한국어 문서](./docs/README.ko.md)
4
6
 
5
7
  ---
6
8
 
7
- ## 🎯 가지 사용 방법
9
+ ## 🎯 Two Ways to Use
8
10
 
9
- ### 1️⃣ **SDK**: 코드에 직접 통합
10
- 당신이 MCP 서버를 개발하고 있다면, SDK를 사용하세요.
11
+ ### 1️⃣ **SDK**: Direct Code Integration
12
+ If you're developing an MCP server, use the SDK.
11
13
 
12
- ### 2️⃣ **CLI**: 코드 수정 없이 래핑
13
- 이미 만들어진 MCP 서버(npm 패키지) 사용한다면, CLI를 사용하세요.
14
+ ### 2️⃣ **CLI**: Zero-Code Wrapper
15
+ If you're using an existing MCP server (npm package), use the CLI.
14
16
 
15
17
  ---
16
18
 
17
- ## 📦 설치
19
+ ## 📦 Installation
18
20
 
19
- ```bash
21
+ \`\`\`bash
20
22
  npm install @awarecorp/mcp-logger
21
- ```
23
+ \`\`\`
22
24
 
23
25
  ---
24
26
 
25
- ## 1️⃣ SDK 사용법
27
+ ## 1️⃣ SDK Usage
26
28
 
27
29
  ### TypeScript
28
30
 
29
- ```typescript
31
+ \`\`\`typescript
30
32
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
31
33
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
32
- import { initMCPLogger } from '@awarecorp/mcp-logger';
34
+ import { trace } from '@awarecorp/mcp-logger';
33
35
 
34
36
  const server = new Server(
35
37
  { name: 'my-server', version: '1.0.0' },
36
38
  { capabilities: { tools: {} } }
37
39
  );
38
40
 
39
- // 🔥 자동 계측 활성화 (이것만 추가하면 됩니다!)
40
- initMCPLogger(server, {
41
+ // 🔥 Enable auto-instrumentation (just add this!)
42
+ trace(server, {
41
43
  apiKey: process.env.AWARE_API_KEY!,
42
44
  serviceName: 'my-awesome-mcp-server',
43
- debug: true, // 디버그 로그 활성화 (선택사항)
45
+ debug: true, // Enable debug logs (optional)
44
46
  });
45
47
 
46
- // 평소처럼 핸들러 등록
48
+ // Register handlers as usual
47
49
  server.setRequestHandler('tools/list', async () => ({
48
50
  tools: [
49
51
  { name: 'read_file', description: 'Read a file' },
@@ -53,29 +55,37 @@ server.setRequestHandler('tools/list', async () => ({
53
55
 
54
56
  server.setRequestHandler('tools/call', async (request) => {
55
57
  const { name, arguments: args } = request.params;
56
- // 이 핸들러는 자동으로 로깅됩니다!
57
- return { result: `Called ${name}` };
58
+
59
+ // Add custom logs (optional)
60
+ trace.addLog({ level: 'info', message: \`Processing \${name}\` });
61
+
62
+ // Business logic
63
+ const result = await processToolCall(name, args);
64
+
65
+ trace.addLog({ level: 'info', message: 'Processing completed' });
66
+
67
+ return { result };
58
68
  });
59
69
 
60
- // stdio transport 시작
70
+ // Start stdio transport
61
71
  const transport = new StdioServerTransport();
62
72
  await server.connect(transport);
63
- ```
73
+ \`\`\`
64
74
 
65
75
  ### JavaScript (CommonJS)
66
76
 
67
- ```javascript
77
+ \`\`\`javascript
68
78
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
69
79
  const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
70
- const { initMCPLogger } = require('@awarecorp/mcp-logger');
80
+ const { trace } = require('@awarecorp/mcp-logger');
71
81
 
72
82
  const server = new Server(
73
83
  { name: 'my-server', version: '1.0.0' },
74
84
  { capabilities: { tools: {} } }
75
85
  );
76
86
 
77
- // 🔥 자동 계측 활성화
78
- initMCPLogger(server, {
87
+ // 🔥 Enable auto-instrumentation
88
+ trace(server, {
79
89
  apiKey: process.env.AWARE_API_KEY,
80
90
  serviceName: 'my-mcp-server',
81
91
  });
@@ -84,60 +94,110 @@ server.setRequestHandler('tools/list', async () => ({
84
94
  tools: [{ name: 'test', description: 'Test tool' }],
85
95
  }));
86
96
 
97
+ server.setRequestHandler('tools/call', async (request) => {
98
+ // Add custom logs
99
+ trace.addLog({ level: 'info', message: 'Tool called' });
100
+
101
+ return { result: 'success' };
102
+ });
103
+
87
104
  const transport = new StdioServerTransport();
88
105
  server.connect(transport);
89
- ```
106
+ \`\`\`
90
107
 
91
108
  ### SDK API
92
109
 
93
- #### `initMCPLogger(server, options)`
110
+ #### \`trace(server, options)\`
94
111
 
95
- MCP 서버에 자동 계측을 추가합니다.
112
+ Add auto-instrumentation to your MCP server.
96
113
 
97
114
  **Parameters:**
98
- - `server` (`MCPServer`): MCP Server 인스턴스
99
- - `options` (`SDKInitOptions`):
100
- - `apiKey` (string, 필수): Aware API
101
- - `serviceName` (string, 옵션): 서비스 이름 (기본값: 자동 생성)
102
- - `debug` (boolean, 옵션): 디버그 로그 활성화 (기본값: false)
103
- - `endpoint` (string, 옵션): 커스텀 OTLP 엔드포인트
115
+ - \`server\` (\`MCPServer\`): MCP Server instance
116
+ - \`options\` (\`TraceOptions\`):
117
+ - \`apiKey\` (string, required): Aware API key
118
+ - \`serviceName\` (string, optional): Service name (default: auto-generated)
119
+ - \`debug\` (boolean, optional): Enable debug logs (default: false)
120
+ - \`endpoint\` (string, optional): Custom OTLP endpoint
121
+
122
+ **Returns:** \`void\`
123
+
124
+ ---
125
+
126
+ #### \`trace.addLog(entry)\`
127
+
128
+ Add custom logs to the currently executing tool handler.
129
+
130
+ **Parameters:**
131
+ - \`entry\` (\`CustomLogEntry\`):
132
+ - \`level\` ('info' | 'warn' | 'error', optional): Log level (default: 'info')
133
+ - \`message\` (string, required): Log message
134
+ - \`metadata\` (object, optional): Additional metadata
135
+
136
+ **Returns:** \`void\`
137
+
138
+ **Example:**
139
+ \`\`\`typescript
140
+ server.setRequestHandler('tools/call', async (request) => {
141
+ trace.addLog({
142
+ level: 'info',
143
+ message: 'Processing started',
144
+ metadata: { userId: '123', action: 'read' }
145
+ });
146
+
147
+ // Business logic...
148
+
149
+ trace.addLog({ level: 'info', message: 'Processing completed' });
150
+
151
+ return { result: 'success' };
152
+ });
153
+ \`\`\`
154
+
155
+ ---
156
+
157
+ #### \`trace.shutdown()\`
158
+
159
+ Shutdown telemetry and flush remaining data.
104
160
 
105
- **Returns:** `void`
161
+ **Returns:** \`Promise<void>\`
106
162
 
107
- **Throws:**
108
- - `Error`: apiKey가 제공되지 않은 경우
109
- - `Error`: server 인스턴스가 제공되지 않은 경우
163
+ **Example:**
164
+ \`\`\`typescript
165
+ process.on('SIGTERM', async () => {
166
+ await trace.shutdown();
167
+ process.exit(0);
168
+ });
169
+ \`\`\`
110
170
 
111
171
  ---
112
172
 
113
- ## 2️⃣ CLI 사용법
173
+ ## 2️⃣ CLI Usage
114
174
 
115
- ### 기본 사용법
175
+ ### Quick Start
116
176
 
117
- ```bash
118
- npx @awarecorp/mcp-logger \
119
- --api-key YOUR_API_KEY \
120
- -- \
177
+ \`\`\`bash
178
+ npx @awarecorp/mcp-logger \\
179
+ --api-key YOUR_API_KEY \\
180
+ -- \\
121
181
  npx @modelcontextprotocol/server-filesystem /path/to/dir
122
- ```
182
+ \`\`\`
123
183
 
124
- `--` 뒤에 원래 사용하던 MCP 서버 명령어를 그대로 입력하면 됩니다.
184
+ Just add your MCP server command after \`--\`.
125
185
 
126
- ### 전역 설치 (선택사항)
186
+ ### Global Installation (Optional)
127
187
 
128
- ```bash
188
+ \`\`\`bash
129
189
  npm install -g @awarecorp/mcp-logger
130
- ```
190
+ \`\`\`
131
191
 
132
- 사용:
133
- ```bash
192
+ Usage:
193
+ \`\`\`bash
134
194
  mcp-logger --api-key YOUR_KEY -- npx server-filesystem /path
135
- ```
195
+ \`\`\`
136
196
 
137
- ### Claude Desktop 설정
197
+ ### Claude Desktop Configuration
138
198
 
139
- #### Before (관측성 없음)
140
- ```json
199
+ #### Before (No Observability)
200
+ \`\`\`json
141
201
  {
142
202
  "mcpServers": {
143
203
  "filesystem": {
@@ -146,10 +206,10 @@ mcp-logger --api-key YOUR_KEY -- npx server-filesystem /path
146
206
  }
147
207
  }
148
208
  }
149
- ```
209
+ \`\`\`
150
210
 
151
- #### After (관측성 추가) ✨
152
- ```json
211
+ #### After (With Observability) ✨
212
+ \`\`\`json
153
213
  {
154
214
  "mcpServers": {
155
215
  "filesystem": {
@@ -166,141 +226,196 @@ mcp-logger --api-key YOUR_KEY -- npx server-filesystem /path
166
226
  }
167
227
  }
168
228
  }
169
- ```
229
+ \`\`\`
170
230
 
171
- **이게 전부입니다!** 코드 수정 없이 모든 요청/응답이 Aware 플랫폼에 기록됩니다.
231
+ **That's it!** All requests/responses are now logged to the Aware platform without code changes.
172
232
 
173
- ### CLI 옵션
233
+ ### CLI Options
174
234
 
175
- #### 필수 옵션
176
- - `--api-key <key>` 또는 `-k <key>`: Aware API (필수)
235
+ #### Required Options
236
+ - \`--api-key <key>\` or \`-k <key>\`: Aware API key (required)
177
237
 
178
- #### 선택 옵션
179
- - `--service-name <name>` 또는 `-s <name>`: 서비스 식별 이름 (기본값: 자동 생성)
180
- - `--debug` 또는 `-d`: 디버그 로그 활성화
181
- - `--endpoint <url>` 또는 `-e <url>`: 커스텀 OTLP 엔드포인트
238
+ #### Optional Options
239
+ - \`--service-name <name>\` or \`-s <name>\`: Service identifier name (default: auto-generated)
240
+ - \`--debug\` or \`-d\`: Enable debug logs
241
+ - \`--endpoint <url>\` or \`-e <url>\`: Custom OTLP endpoint
182
242
 
183
- ### CLI 사용 예시
243
+ ### CLI Usage Examples
184
244
 
185
- #### 파일시스템 서버
186
- ```bash
187
- npx @awarecorp/mcp-logger \
188
- --api-key abc123 \
189
- --service-name my-filesystem \
190
- -- \
245
+ #### Filesystem Server
246
+ \`\`\`bash
247
+ npx @awarecorp/mcp-logger \\
248
+ --api-key abc123 \\
249
+ --service-name my-filesystem \\
250
+ -- \\
191
251
  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
- -- \
252
+ \`\`\`
253
+
254
+ #### GitHub Server
255
+ \`\`\`bash
256
+ npx @awarecorp/mcp-logger \\
257
+ -k abc123 \\
258
+ -s github-integration \\
259
+ -- \\
200
260
  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
- -- \
261
+ \`\`\`
262
+
263
+ #### Brave Search Server (Debug Mode)
264
+ \`\`\`bash
265
+ npx @awarecorp/mcp-logger \\
266
+ --api-key abc123 \\
267
+ --service-name brave-search \\
268
+ --debug \\
269
+ -- \\
210
270
  npx -y @modelcontextprotocol/server-brave-search
211
- ```
271
+ \`\`\`
212
272
 
213
273
  ---
214
274
 
215
- ## 🏗️ 아키텍처
275
+ ## 🏗️ Architecture
216
276
 
217
- 패키지는 가지 레이어로 구성되어 있습니다:
277
+ This package consists of three layers:
218
278
 
219
- ```
279
+ \`\`\`
220
280
  @awarecorp/mcp-logger
221
- ├── SDK (공개 API)
222
- │ ├── initMCPLogger() - MCP 서버 계측
223
- └── shutdown() - 텔레메트리 종료
281
+ ├── SDK (Public API)
282
+ │ ├── trace() - Instrument MCP server
283
+ ├── trace.addLog() - Add custom logs
284
+ │ └── trace.shutdown() - Shutdown telemetry
224
285
 
225
- ├── CLI (바이너리만 접근 가능)
226
- └── mcp-logger - CLI 래퍼
286
+ ├── CLI (Binary-only access)
287
+ ├── main.ts - CLI entry point
288
+ │ ├── interceptor.ts - Stream interception
289
+ │ └── pairing.ts - Request-Response matching
227
290
 
228
- └── Shared (내부 구현)
229
- ├── telemetry - OpenTelemetry 초기화
230
- ├── span-utils - Span 유틸리티
231
- └── config - 공통 설정
232
- ```
291
+ └── Core (Internal implementation)
292
+ ├── telemetry - OpenTelemetry initialization
293
+ ├── span-builder - Unified Span creation
294
+ ├── span-utils - Span utilities
295
+ ├── config - Tracked method configuration
296
+ └── types - Common type definitions
297
+ \`\`\`
298
+
299
+ ### Key Features
300
+
301
+ #### ✅ Request-Response Pairing
302
+ - Matches requests and responses by ID into a **single span**
303
+ - Automatic cleanup of incomplete requests with 30s timeout
304
+
305
+ #### ✅ Selective Tracking
306
+ - Only tracks \`tools/call\` and \`tools/list\` (removes unnecessary overhead)
307
+ - Configurable to extend tracking targets
308
+
309
+ #### ✅ Custom Events
310
+ - Add custom events inside handlers with \`trace.addLog()\`
311
+ - Automatically linked to the currently executing span via context
233
312
 
234
- ### 중요한 설계 원칙
313
+ #### Elasticsearch Optimization
314
+ - Extracts tool name and arguments as top-level fields
315
+ - Fast searching with \`mcp.tool.name\`, \`mcp.tool.arguments\`, etc.
235
316
 
236
- 1. **SDK만 공개 API로 노출**: `import`로는 SDK만 접근 가능
237
- 2. **CLI는 바이너리로만 접근**: `npx` 또는 `mcp-logger` 명령어로만 사용
238
- 3. **공통 코드 재사용**: OpenTelemetry 로직은 shared 레이어에서 통합 관리
239
- 4. **타입 안전성**: CLI는 타입 정의가 노출되지 않음
317
+ ### Design Principles
318
+
319
+ 1. **SDK-only public API**: Only SDK accessible via \`import\`
320
+ 2. **CLI binary-only access**: Use via \`npx\` or \`mcp-logger\` command only
321
+ 3. **Shared code reuse**: OpenTelemetry logic unified in core layer
322
+ 4. **Type safety**: CLI type definitions not exposed
323
+ 5. **dd-trace style API**: Intuitive usage with \`trace()\` namespace
240
324
 
241
325
  ---
242
326
 
243
- ## 🔧 개발
327
+ ## 🔧 Development
244
328
 
245
- ### 빌드
329
+ ### Build
246
330
 
247
- ```bash
331
+ \`\`\`bash
248
332
  npm run build
249
- ```
333
+ \`\`\`
334
+
335
+ ### Tests
336
+
337
+ \`\`\`bash
338
+ npm test # Run all tests
339
+ npm run test:watch # Watch mode
340
+ \`\`\`
250
341
 
251
- ### 로컬 테스트
342
+ ### Local Testing
252
343
 
253
- #### SDK 테스트
254
- ```typescript
344
+ #### SDK Testing
345
+ \`\`\`typescript
255
346
  // test.ts
256
347
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
257
- import { initMCPLogger } from './src/index.js';
348
+ import { trace } from './src/index.js';
258
349
 
259
350
  const server = new Server({ name: 'test', version: '1.0.0' }, { capabilities: {} });
260
- initMCPLogger(server, { apiKey: 'test', debug: true });
261
- ```
351
+ trace(server, { apiKey: 'test', debug: true });
262
352
 
263
- #### CLI 테스트
264
- ```bash
353
+ server.setRequestHandler('tools/call', async (request) => {
354
+ trace.addLog({ level: 'info', message: 'Test handler called' });
355
+ return { result: 'success' };
356
+ });
357
+ \`\`\`
358
+
359
+ #### CLI Testing
360
+ \`\`\`bash
265
361
  npm run build
266
362
  node bin/mcp-logger.js --help
267
- ```
363
+ \`\`\`
268
364
 
269
365
  ---
270
366
 
271
- ## 📝 라이선스
367
+ ## 📝 License
272
368
 
273
369
  MIT
274
370
 
275
371
  ---
276
372
 
277
- ## 🤝 기여
373
+ ## 🤝 Contributing
278
374
 
279
- 이슈와 PR은 언제나 환영합니다!
375
+ Issues and PRs are always welcome!
280
376
 
281
- 자세한 개발 가이드는 [DEVELOPMENT.md](./DEVELOPMENT.md) 참조하세요.
377
+ See [DEVELOPMENT.md](./DEVELOPMENT.md) for detailed development guidelines.
282
378
 
283
379
  ---
284
380
 
285
- ## 📦 배포 (Maintainers Only)
381
+ ## 📦 Publishing (Maintainers Only)
286
382
 
287
- ### Stable 버전 배포
288
- ```bash
289
- npm run version:patch # 또는 minor, major
290
- npm run publish:stable
291
- ```
383
+ **Recommended:** Automated deployment with GitHub Actions
292
384
 
293
- ### Dev 버전 배포
294
- ```bash
385
+ ### Automated Deployment (GitHub Actions) ⭐
386
+ \`\`\`bash
387
+ # Dev version (develop branch)
388
+ git checkout develop
295
389
  npm run version:dev
296
- npm run publish:dev
297
- ```
390
+ git push origin develop --tags
391
+
392
+ # Production version (main branch)
393
+ git checkout main
394
+ npm run version:patch # or minor, major
395
+ git push origin main --tags
396
+ \`\`\`
298
397
 
299
- 자세한 내용은 [DEVELOPMENT.md](./DEVELOPMENT.md#배포)를 참조하세요.
398
+ GitHub Actions will automatically build and publish to npm.
399
+
400
+ ### Manual Deployment (Not Recommended)
401
+ \`\`\`bash
402
+ # Dev version
403
+ npm run build
404
+ npm publish --access public --tag dev
405
+
406
+ # Production version
407
+ npm run build
408
+ npm publish --access public
409
+ \`\`\`
300
410
 
301
411
  ---
302
412
 
303
- ## 📞 지원
413
+ ## 🌐 Links
414
+
415
+ - **Aware Platform**: https://aware.mcypher.com
416
+ - **MCP Specification**: https://modelcontextprotocol.io
417
+ - **GitHub Repository**: https://github.com/awarecorp/mcp-logger
418
+
419
+ ---
304
420
 
305
- - 문서: [https://aware.mcypher.com/docs](https://aware.mcypher.com/docs)
306
- - 이슈: [GitHub Issues](https://github.com/your-org/mcp-logger/issues)
421
+ Made with ❤️ by [Aware](https://aware.mcypher.com)
package/bin/mcp-logger.js CHANGED
@@ -6,4 +6,4 @@
6
6
  * 컴파일된 CLI를 실행합니다.
7
7
  */
8
8
 
9
- import '../dist/cli/cli.js';
9
+ import '../dist/cli/main.js';
@@ -1 +1 @@
1
- import{Transform as e}from"stream";import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";class r extends e{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){this.debug&&console.error("[MCP Logger CLI] Tracer not initialized")}_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)}}}export{r as MessageInterceptor};
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};
@@ -0,0 +1,2 @@
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 s}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as o}from"@opentelemetry/resources";import{trace as n,SpanStatusCode as i}from"@opentelemetry/api";import{Transform as c}from"stream";const a={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let p=null,m=null,u=!1,g=null;function l(){return m}function d(){return g}async function h(){if(p)try{await p.shutdown(),p=null,m=null,u=!1,g=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}class f extends c{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)}}}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 I(e){const r=l();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const t=`mcp.${e.method}`;r.startActiveSpan(t,r=>{try{const t={"mcp.method":e.method,"mcp.source":e.source,"mcp.transport":e.transport,"mcp.log_type":"request-response","mcp.request.id":String(e.request.id),"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":S(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const r=e.request.params.name,s=e.request.params.arguments;if(r&&(t["mcp.tool.name"]=r),s){const e=S(s);e&&(t["mcp.tool.arguments"]=e,t["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if(void 0!==e.response.result){const r=S(e.response.result);r&&(t["mcp.response.result"]=r,t["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const r=S(e.request.headers);r&&(t["mcp.headers"]=r,t["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=d();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(e.customEvents&&e.customEvents.length>0&&(t["mcp.events"]=JSON.stringify(e.customEvents),t["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{r.addEvent(`custom.${e.level||"info"}`,{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){t["mcp.error"]=!0;const s=e.response.error instanceof Error?e.response.error.message:String(e.response.error);t["mcp.error.message"]=s,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:i.ERROR,message:s})}else r.setStatus({code:i.OK});!function(e,r){for(const[t,s]of Object.entries(r))null!=s&&e.setAttribute(t,s)}(r,t)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),r.setStatus({code:i.ERROR,message:String(e)})}finally{r.end()}})}class y{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(e){var r;r=e.method,a.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,o){I({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:o})}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()}}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,i)=>{try{const c=!0;!function(e){if(u)return console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),m;const r=e.serviceName||`${a.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${e.endpoint||a.ENDPOINT}`),console.error(`[MCP Logger] Service: ${r}`),p=new t({resource:new o({"service.name":r,"service.version":a.SDK_VERSION}),traceExporter:new s({url:e.endpoint||a.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),p.start(),m=n.getTracer("mcp-logger",a.SDK_VERSION),u=!0,console.error("[MCP Logger] Telemetry initialized successfully");const i=async()=>{console.error("[MCP Logger] Shutting down telemetry..."),await h()};process.once("SIGTERM",i),process.once("SIGINT",i)}({...i,debug:c}),console.error("[MCP Logger CLI] Starting MCP server:",r.join(" "));const[l,...d]=r;!function(e,r,t){g={command:e,args:r,env:t}}(l,d,{...process.env});const S=e(l,d,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),I=new y(c),C=new f("request",I,c);process.stdin.pipe(C).pipe(S.stdin);const E=new f("response",I,c);S.stdout.pipe(E).pipe(process.stdout),S.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),I.clear(),await h(),process.exit(1)}),S.on("exit",async(e,r)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),I.clear(),await h(),process.exit(e||0)});let q=!1;const v=async e=>{q||(q=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),S.kill(e),await Promise.race([new Promise(e=>S.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await h(),process.exit(0))};process.on("SIGTERM",()=>v("SIGTERM")),process.on("SIGINT",()=>v("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await h(),process.exit(1)}}),r.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
+ const t={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};function E(E){return t.TRACKED_METHODS.includes(E)}export{t as CONFIG,E as shouldTrackMethod};
@@ -0,0 +1 @@
1
+ import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}export{e as createPairedSpan};
@@ -0,0 +1 @@
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,l=null,s=!1,a=null;function u(a){if(s)return a.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),l;const u=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: ${u}`)),c=new e({resource:new o({"service.name":u,"service.version":i}),traceExporter:new r({url:a.endpoint||n,headers:{"x-api-key":a.apiKey}})}),c.start(),l=t.getTracer("mcp-logger",i),s=!0,a.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const g=async()=>{a.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await y()};return process.once("SIGTERM",g),process.once("SIGINT",g),l}function g(){return l}function m(){return s}function p(e,r,o){a={command:e,args:r,env:o}}function d(){return a}async function y(){if(c)try{await c.shutdown(),c=null,l=null,s=!1,a=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}export{d as getCLIServerInfo,g as getTracer,u as initializeTelemetry,m as isInitializedTelemetry,p as setCLIServerInfo,y 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
- * SDK 초기화 옵션 (TelemetryOptions 확장)
57
+ * Trace 옵션
36
58
  */
37
- interface SDKInitOptions extends TelemetryOptions {
59
+ interface TraceOptions extends TelemetryOptions {
38
60
  }
39
61
  /**
40
- * MCP Server 타입 (export)
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
- * Telemetry 종료
66
+ * MCP Server 타입 (재export)
53
67
  */
54
- declare function shutdownTelemetry(): Promise<void>;
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
- * MCP 서버에 자동 계측을 추가합니다.
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 { Server } from '@modelcontextprotocol/sdk/server/index.js';
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
- * initMCPLogger(server, {
83
+ * // 서버 계측
84
+ * trace(server, {
85
85
  * apiKey: 'your-api-key',
86
- * serviceName: 'my-mcp-server',
87
- * debug: true,
86
+ * serviceName: 'my-service',
88
87
  * });
89
88
  *
89
+ * // Handler 내부에서 커스텀 로그 추가
90
90
  * server.setRequestHandler('tools/call', async (request) => {
91
- * // 이 핸들러는 자동으로 로깅됩니다
92
- * return { result: 'success' };
93
- * });
94
- * ```
91
+ * trace.addLog({
92
+ * level: 'info',
93
+ * message: 'Processing started',
94
+ * metadata: { id: request.params.arguments.id },
95
+ * });
95
96
  *
96
- * @example
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
- * const server = new Server({ name: 'my-server', version: '1.0.0' }, {
103
- * capabilities: { tools: {} }
104
- * });
99
+ * trace.addLog({
100
+ * level: 'info',
101
+ * message: 'Processing completed',
102
+ * metadata: { duration: 100 },
103
+ * });
105
104
  *
106
- * initMCPLogger(server, {
107
- * apiKey: process.env.API_KEY,
108
- * serviceName: 'my-mcp-server',
105
+ * return result;
109
106
  * });
110
107
  * ```
111
108
  */
112
- declare function initMCPLogger(server: MCPServer, options: SDKInitOptions): void;
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 { initMCPLogger, shutdownTelemetry as shutdown };
115
- export type { MCPServer, SDKInitOptions };
136
+ export { trace };
137
+ export type { CustomLogEntry, MCPServer, TraceOptions };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{NodeSDK as e}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as r}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as t}from"@opentelemetry/resources";import{trace as o,SpanStatusCode as n}from"@opentelemetry/api";const s="https://aware.mcypher.com/v1/traces",c="1.0.0";let i=null,a=null,l=!1;function u(){return a}async function g(){if(i)try{await i.shutdown(),i=null,a=null,l=!1}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function m(e,r){for(const[t,o]of Object.entries(r))null!=o&&e.setAttribute(t,o)}function p(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)}}function d(e,r=1e4){try{const t=JSON.stringify(e);return t.length>r?t.slice(0,r)+"... (truncated)":t}catch(e){return null}}function h(h,f){if(!f.apiKey)throw new Error("[MCP Logger SDK] apiKey is required");if(!h)throw new Error("[MCP Logger SDK] server instance is required");!function(n){if(l)return n.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),a;const u=n.serviceName||`mcp-server-${Math.random().toString(36).slice(2,8)}`;n.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${n.endpoint||s}`),console.error(`[MCP Logger] Service: ${u}`)),i=new e({resource:new t({"service.name":u,"service.version":c}),traceExporter:new r({url:n.endpoint||s,headers:{"x-api-key":n.apiKey}})}),i.start(),a=o.getTracer("mcp-logger",c),l=!0,n.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const m=async()=>{n.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await g()};process.once("SIGTERM",m),process.once("SIGINT",m)}(f),function(e,r=!1){const t=u();if(!t)throw new Error("[MCP Logger SDK] Tracer not initialized. Call initMCPLogger first.");const o=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const c=e.shape?.method,i=c?._def?.value||"unknown";return r&&console.log(`[MCP Logger SDK] Instrumenting handler: ${i}`),o(e,async(e,o)=>{const c=`mcp.${i}`;return await t.startActiveSpan(c,{attributes:{"mcp.method":i,"mcp.request.method":e.method}},async t=>{const c=Date.now();try{const i=d(e.params||{});m(t,{"mcp.request.id":"unknown","mcp.request.params.size":i?.length}),i&&i.length<1e3&&t.setAttribute("mcp.request.params",i),p(t,"mcp.request.received",{"request.method":e.method,"request.timestamp":c},r),r&&console.log("[MCP Logger SDK] Request received:",{method:e.method,params:e.params});const a=await s(e,o),l=Date.now(),u=l-c,g=d(a);return m(t,{"mcp.duration_ms":u,"mcp.response.size":g?.length}),g&&g.length<1e3&&t.setAttribute("mcp.response.result",g),p(t,"mcp.response.sent",{"response.timestamp":l,"response.duration_ms":u},r),function(e){e.setStatus({code:n.OK})}(t),r&&console.log(`[MCP Logger SDK] Response sent (${u}ms):`,a),a}catch(e){throw function(e,r,t){const o=r instanceof Error?r.message:"Unknown error",s=r instanceof Error?r.constructor.name:"Error";r instanceof Error&&e.recordException(r),e.setStatus({code:n.ERROR,message:o}),e.setAttribute("error",!0),e.setAttribute("error.type",s),e.setAttribute("error.message",o),t&&console.error("[MCP Logger] Error recorded in span:",r)}(t,e,r),r&&console.error("[MCP Logger SDK] Error occurred:",e),e}finally{t.end()}})})},r&&console.log("[MCP Logger SDK] Server instrumentation complete")}(h,f.debug),f.debug&&console.log("[MCP Logger SDK] ✓ Initialization complete")}export{h as initMCPLogger,g as shutdown};
1
+ import{trace as e,SpanStatusCode as t,context as r}from"@opentelemetry/api";import{NodeSDK as s}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as o}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as n}from"@opentelemetry/resources";const a={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let c=null,i=null,m=!1,l=null;function p(){return i}function u(){return l}async function g(){if(c)try{await c.shutdown(),c=null,i=null,m=!1,l=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function d(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function f(e){const r=p();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const s=`mcp.${e.method}`;r.startActiveSpan(s,r=>{try{const s={"mcp.method":e.method,"mcp.source":e.source,"mcp.transport":e.transport,"mcp.log_type":"request-response","mcp.request.id":String(e.request.id),"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":d(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const t=e.request.params.name,r=e.request.params.arguments;if(t&&(s["mcp.tool.name"]=t),r){const e=d(r);e&&(s["mcp.tool.arguments"]=e,s["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==e.response.result){const t=d(e.response.result);t&&(s["mcp.response.result"]=t,s["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const t=d(e.request.headers);t&&(s["mcp.headers"]=t,s["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=u();e&&(s["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(e.customEvents&&e.customEvents.length>0&&(s["mcp.events"]=JSON.stringify(e.customEvents),s["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{r.addEvent(`custom.${e.level||"info"}`,{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){s["mcp.error"]=!0;const o=e.response.error instanceof Error?e.response.error.message:String(e.response.error);s["mcp.error.message"]=o,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:t.ERROR,message:o})}else r.setStatus({code:t.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(r,s)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),r.setStatus({code:t.ERROR,message:String(e)})}finally{r.end()}})}const h=Symbol("mcp-logger.customEvents");const E=Object.assign(function(t,l){if(!l.apiKey)throw new Error("[mcp-logger] apiKey is required");if(!t)throw new Error("[mcp-logger] server instance is required");!function(t){if(m)return t.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),i;const r=t.serviceName||`${a.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;t.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${t.endpoint||a.ENDPOINT}`),console.error(`[MCP Logger] Service: ${r}`)),c=new s({resource:new n({"service.name":r,"service.version":a.SDK_VERSION}),traceExporter:new o({url:t.endpoint||a.ENDPOINT,headers:{"x-api-key":t.apiKey}})}),c.start(),i=e.getTracer("mcp-logger",a.SDK_VERSION),m=!0,t.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const l=async()=>{t.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await g()};process.once("SIGTERM",l),process.once("SIGINT",l)}(l),function(e,t){const s=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,o){const n=e.shape?.method,c=n?._def?.value||"unknown";return function(e){return a.TRACKED_METHODS.includes(e)}(c)?(t.debug&&console.log(`[mcp-logger] Instrumenting handler: ${c}`),s(e,async(e,s)=>{const n=Date.now(),a=`${c}-${n}-${Math.random().toString(36).slice(2,8)}`,i=[],m=r.active().setValue(h,i);try{const l=await r.with(m,async()=>await o(e,s)),p=Date.now();return f({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:n,params:e.params},response:{timestamp:p,result:l},duration:p-n,customEvents:i.length>0?i:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${p-n}ms):`,{method:c,customEvents:i.length}),l}catch(r){const s=Date.now();throw f({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:i.length>0?i:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})):(t.debug&&console.log(`[mcp-logger] Skipping instrumentation for method: ${c}`),s(e,o))},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(t,l),l.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(t){const s=r.active().getValue(h);if(!s)return void console.warn("[mcp-logger] addLog called outside of handler context");const o={level:t.level||"info",message:t.message,metadata:t.metadata,timestamp:t.timestamp||Date.now()};s.push(o);const n=e.getSpan(r.active());n&&n.addEvent(`custom.${o.level}`,{message:o.message,metadata:JSON.stringify(o.metadata||{}),timestamp:o.timestamp})},async shutdown(){await g()}});export{E as trace};
package/dist/sdk/index.js CHANGED
@@ -1 +1 @@
1
- import{NodeSDK as e}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as r}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as t}from"@opentelemetry/resources";import{trace as o,SpanStatusCode as n}from"@opentelemetry/api";const s="https://aware.mcypher.com/v1/traces",c="1.0.0";let i=null,a=null,l=!1;function u(){return a}async function g(){if(i)try{await i.shutdown(),i=null,a=null,l=!1}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function m(e,r){for(const[t,o]of Object.entries(r))null!=o&&e.setAttribute(t,o)}function p(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)}}function d(e,r=1e4){try{const t=JSON.stringify(e);return t.length>r?t.slice(0,r)+"... (truncated)":t}catch(e){return null}}function h(h,f){if(!f.apiKey)throw new Error("[MCP Logger SDK] apiKey is required");if(!h)throw new Error("[MCP Logger SDK] server instance is required");!function(n){if(l)return n.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),a;const u=n.serviceName||`mcp-server-${Math.random().toString(36).slice(2,8)}`;n.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${n.endpoint||s}`),console.error(`[MCP Logger] Service: ${u}`)),i=new e({resource:new t({"service.name":u,"service.version":c}),traceExporter:new r({url:n.endpoint||s,headers:{"x-api-key":n.apiKey}})}),i.start(),a=o.getTracer("mcp-logger",c),l=!0,n.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const m=async()=>{n.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await g()};process.once("SIGTERM",m),process.once("SIGINT",m)}(f),function(e,r=!1){const t=u();if(!t)throw new Error("[MCP Logger SDK] Tracer not initialized. Call initMCPLogger first.");const o=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const c=e.shape?.method,i=c?._def?.value||"unknown";return r&&console.log(`[MCP Logger SDK] Instrumenting handler: ${i}`),o(e,async(e,o)=>{const c=`mcp.${i}`;return await t.startActiveSpan(c,{attributes:{"mcp.method":i,"mcp.request.method":e.method}},async t=>{const c=Date.now();try{const i=d(e.params||{});m(t,{"mcp.request.id":"unknown","mcp.request.params.size":i?.length}),i&&i.length<1e3&&t.setAttribute("mcp.request.params",i),p(t,"mcp.request.received",{"request.method":e.method,"request.timestamp":c},r),r&&console.log("[MCP Logger SDK] Request received:",{method:e.method,params:e.params});const a=await s(e,o),l=Date.now(),u=l-c,g=d(a);return m(t,{"mcp.duration_ms":u,"mcp.response.size":g?.length}),g&&g.length<1e3&&t.setAttribute("mcp.response.result",g),p(t,"mcp.response.sent",{"response.timestamp":l,"response.duration_ms":u},r),function(e){e.setStatus({code:n.OK})}(t),r&&console.log(`[MCP Logger SDK] Response sent (${u}ms):`,a),a}catch(e){throw function(e,r,t){const o=r instanceof Error?r.message:"Unknown error",s=r instanceof Error?r.constructor.name:"Error";r instanceof Error&&e.recordException(r),e.setStatus({code:n.ERROR,message:o}),e.setAttribute("error",!0),e.setAttribute("error.type",s),e.setAttribute("error.message",o),t&&console.error("[MCP Logger] Error recorded in span:",r)}(t,e,r),r&&console.error("[MCP Logger SDK] Error occurred:",e),e}finally{t.end()}})})},r&&console.log("[MCP Logger SDK] Server instrumentation complete")}(h,f.debug),f.debug&&console.log("[MCP Logger SDK] ✓ Initialization complete")}export{h as initMCPLogger,g as shutdown};
1
+ import{trace as e,SpanStatusCode as t,context as r}from"@opentelemetry/api";import{NodeSDK as s}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as o}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as n}from"@opentelemetry/resources";const a={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let c=null,i=null,m=!1,l=null;function p(){return i}function u(){return l}async function g(){if(c)try{await c.shutdown(),c=null,i=null,m=!1,l=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function d(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function f(e){const r=p();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const s=`mcp.${e.method}`;r.startActiveSpan(s,r=>{try{const s={"mcp.method":e.method,"mcp.source":e.source,"mcp.transport":e.transport,"mcp.log_type":"request-response","mcp.request.id":String(e.request.id),"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":d(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const t=e.request.params.name,r=e.request.params.arguments;if(t&&(s["mcp.tool.name"]=t),r){const e=d(r);e&&(s["mcp.tool.arguments"]=e,s["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==e.response.result){const t=d(e.response.result);t&&(s["mcp.response.result"]=t,s["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const t=d(e.request.headers);t&&(s["mcp.headers"]=t,s["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=u();e&&(s["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(e.customEvents&&e.customEvents.length>0&&(s["mcp.events"]=JSON.stringify(e.customEvents),s["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{r.addEvent(`custom.${e.level||"info"}`,{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){s["mcp.error"]=!0;const o=e.response.error instanceof Error?e.response.error.message:String(e.response.error);s["mcp.error.message"]=o,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:t.ERROR,message:o})}else r.setStatus({code:t.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(r,s)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),r.setStatus({code:t.ERROR,message:String(e)})}finally{r.end()}})}const h=Symbol("mcp-logger.customEvents");const E=Object.assign(function(t,l){if(!l.apiKey)throw new Error("[mcp-logger] apiKey is required");if(!t)throw new Error("[mcp-logger] server instance is required");!function(t){if(m)return t.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),i;const r=t.serviceName||`${a.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;t.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${t.endpoint||a.ENDPOINT}`),console.error(`[MCP Logger] Service: ${r}`)),c=new s({resource:new n({"service.name":r,"service.version":a.SDK_VERSION}),traceExporter:new o({url:t.endpoint||a.ENDPOINT,headers:{"x-api-key":t.apiKey}})}),c.start(),i=e.getTracer("mcp-logger",a.SDK_VERSION),m=!0,t.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const l=async()=>{t.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await g()};process.once("SIGTERM",l),process.once("SIGINT",l)}(l),function(e,t){const s=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,o){const n=e.shape?.method,c=n?._def?.value||"unknown";return function(e){return a.TRACKED_METHODS.includes(e)}(c)?(t.debug&&console.log(`[mcp-logger] Instrumenting handler: ${c}`),s(e,async(e,s)=>{const n=Date.now(),a=`${c}-${n}-${Math.random().toString(36).slice(2,8)}`,i=[],m=r.active().setValue(h,i);try{const l=await r.with(m,async()=>await o(e,s)),p=Date.now();return f({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:n,params:e.params},response:{timestamp:p,result:l},duration:p-n,customEvents:i.length>0?i:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${p-n}ms):`,{method:c,customEvents:i.length}),l}catch(r){const s=Date.now();throw f({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:i.length>0?i:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})):(t.debug&&console.log(`[mcp-logger] Skipping instrumentation for method: ${c}`),s(e,o))},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(t,l),l.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(t){const s=r.active().getValue(h);if(!s)return void console.warn("[mcp-logger] addLog called outside of handler context");const o={level:t.level||"info",message:t.message,metadata:t.metadata,timestamp:t.timestamp||Date.now()};s.push(o);const n=e.getSpan(r.active());n&&n.addEvent(`custom.${o.level}`,{message:o.message,metadata:JSON.stringify(o.metadata||{}),timestamp:o.timestamp})},async shutdown(){await g()}});export{E as trace};
@@ -1 +1 @@
1
- import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";import"@opentelemetry/api";function e(e,t=!1){throw new Error("[MCP Logger SDK] Tracer not initialized. Call initMCPLogger first.")}export{e as instrumentMCPServer};
1
+ import{context as e}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const t={TRACKED_METHODS:["tools/call","tools/list"]};function o(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}const n=Symbol("mcp-logger.customEvents");function r(r,c){const s=r.setRequestHandler.bind(r);r.setRequestHandler=function(r,l){const a=r.shape?.method,i=a?._def?.value||"unknown";return function(e){return t.TRACKED_METHODS.includes(e)}(i)?(c.debug&&console.log(`[mcp-logger] Instrumenting handler: ${i}`),s(r,async(t,r)=>{const s=Date.now(),a=(Math.random().toString(36).slice(2,8),[]),m=e.active().setValue(n,a);try{const n=await e.with(m,async()=>await l(t,r)),u=Date.now();return o(t.params),c.debug&&console.log(`[mcp-logger] Request completed (${u-s}ms):`,{method:i,customEvents:a.length}),n}catch(e){throw o(t.params),c.debug&&console.error("[mcp-logger] Request failed:",e),e}})):(c.debug&&console.log(`[mcp-logger] Skipping instrumentation for method: ${i}`),s(r,l))},c.debug&&console.log("[mcp-logger] Server instrumentation complete")}function c(){return e.active().getValue(n)}export{c as getCustomEventsFromContext,r as instrumentMCPServer};
@@ -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
- * SDK 초기화 옵션 (TelemetryOptions 확장)
59
+ * Trace 옵션
38
60
  */
39
- interface SDKInitOptions extends TelemetryOptions {
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, SDKInitOptions };
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-dev.2",
3
+ "version": "0.0.2-dev.5",
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",g="1.0.0";let d=null,p=null,u=!1;function l(){return p}async function m(){if(d)try{await d.shutdown(),d=null,p=null,u=!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("-d, --debug","Enable debug logging",!1).option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(r,n)=>{try{!function(e){if(u)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),p;const r=e.serviceName||`mcp-server-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${e.endpoint||a}`),console.error(`[MCP Logger] Service: ${r}`)),d=new t({resource:new s({"service.name":r,"service.version":g}),traceExporter:new o({url:e.endpoint||a,headers:{"x-api-key":e.apiKey}})}),d.start(),p=i.getTracer("mcp-logger",g),u=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const n=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await m()};process.once("SIGTERM",n),process.once("SIGINT",n)}(n),n.debug&&console.error("[MCP Logger CLI] Starting MCP server:",r.join(" "));const[c,...l]=r,h=e(c,l,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),b=new f("request",n.debug);process.stdin.pipe(b).pipe(h.stdin);const C=new f("response",n.debug);h.stdout.pipe(C).pipe(process.stdout),h.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),await m(),process.exit(1)}),h.on("exit",async(e,r)=>{n.debug&&console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),await m(),process.exit(e||0)});let y=!1;const L=async e=>{y||(y=!0,n.debug&&console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),h.kill(e),await Promise.race([new Promise(e=>h.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await m(),process.exit(0))};process.on("SIGTERM",()=>L("SIGTERM")),process.on("SIGINT",()=>L("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await m(),process.exit(1)}}),r.parse();
@@ -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{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};
@@ -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 };
File without changes