@awarecorp/mcp-logger 0.0.2-dev.3 → 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 +250 -146
- 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/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/telemetry.js +0 -1
- package/dist/shared/types.d.ts +0 -53
- /package/dist/{shared → core}/span-utils.js +0 -0
package/README.md
CHANGED
|
@@ -1,49 +1,51 @@
|
|
|
1
1
|
# @awarecorp/mcp-logger
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
11
|
+
### 1️⃣ **SDK**: Direct Code Integration
|
|
12
|
+
If you're developing an MCP server, use the SDK.
|
|
11
13
|
|
|
12
|
-
### 2️⃣ **CLI**:
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
+
\`\`\`typescript
|
|
30
32
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
31
33
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
32
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
+
\`\`\`javascript
|
|
68
78
|
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
69
79
|
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
70
|
-
const {
|
|
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
|
-
|
|
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
|
-
####
|
|
110
|
+
#### \`trace(server, options)\`
|
|
111
|
+
|
|
112
|
+
Add auto-instrumentation to your MCP server.
|
|
113
|
+
|
|
114
|
+
**Parameters:**
|
|
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)\`
|
|
94
127
|
|
|
95
|
-
|
|
128
|
+
Add custom logs to the currently executing tool handler.
|
|
96
129
|
|
|
97
130
|
**Parameters:**
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
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()\`
|
|
104
158
|
|
|
105
|
-
|
|
159
|
+
Shutdown telemetry and flush remaining data.
|
|
106
160
|
|
|
107
|
-
**
|
|
108
|
-
|
|
109
|
-
|
|
161
|
+
**Returns:** \`Promise<void>\`
|
|
162
|
+
|
|
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
|
-
|
|
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
|
-
|
|
184
|
+
Just add your MCP server command after \`--\`.
|
|
125
185
|
|
|
126
|
-
###
|
|
186
|
+
### Global Installation (Optional)
|
|
127
187
|
|
|
128
|
-
|
|
188
|
+
\`\`\`bash
|
|
129
189
|
npm install -g @awarecorp/mcp-logger
|
|
130
|
-
|
|
190
|
+
\`\`\`
|
|
131
191
|
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
+
#### After (With Observability) ✨
|
|
212
|
+
\`\`\`json
|
|
153
213
|
{
|
|
154
214
|
"mcpServers": {
|
|
155
215
|
"filesystem": {
|
|
@@ -166,152 +226,196 @@ mcp-logger --api-key YOUR_KEY -- npx server-filesystem /path
|
|
|
166
226
|
}
|
|
167
227
|
}
|
|
168
228
|
}
|
|
169
|
-
|
|
229
|
+
\`\`\`
|
|
170
230
|
|
|
171
|
-
|
|
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
|
-
-
|
|
235
|
+
#### Required Options
|
|
236
|
+
- \`--api-key <key>\` or \`-k <key>\`: Aware API key (required)
|
|
177
237
|
|
|
178
|
-
####
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
222
|
-
│ ├──
|
|
223
|
-
│
|
|
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
|
-
│
|
|
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
|
-
└──
|
|
229
|
-
├── telemetry
|
|
230
|
-
├── span-
|
|
231
|
-
|
|
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
|
|
233
308
|
|
|
234
|
-
|
|
309
|
+
#### ✅ Custom Events
|
|
310
|
+
- Add custom events inside handlers with \`trace.addLog()\`
|
|
311
|
+
- Automatically linked to the currently executing span via context
|
|
235
312
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
313
|
+
#### ✅ Elasticsearch Optimization
|
|
314
|
+
- Extracts tool name and arguments as top-level fields
|
|
315
|
+
- Fast searching with \`mcp.tool.name\`, \`mcp.tool.arguments\`, etc.
|
|
316
|
+
|
|
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
|
-
|
|
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
|
-
|
|
344
|
+
#### SDK Testing
|
|
345
|
+
\`\`\`typescript
|
|
255
346
|
// test.ts
|
|
256
347
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
257
|
-
import {
|
|
348
|
+
import { trace } from './src/index.js';
|
|
258
349
|
|
|
259
350
|
const server = new Server({ name: 'test', version: '1.0.0' }, { capabilities: {} });
|
|
260
|
-
|
|
261
|
-
```
|
|
351
|
+
trace(server, { apiKey: 'test', debug: true });
|
|
262
352
|
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
375
|
+
Issues and PRs are always welcome!
|
|
280
376
|
|
|
281
|
-
|
|
377
|
+
See [DEVELOPMENT.md](./DEVELOPMENT.md) for detailed development guidelines.
|
|
282
378
|
|
|
283
379
|
---
|
|
284
380
|
|
|
285
|
-
## 📦
|
|
381
|
+
## 📦 Publishing (Maintainers Only)
|
|
286
382
|
|
|
287
|
-
|
|
383
|
+
**Recommended:** Automated deployment with GitHub Actions
|
|
288
384
|
|
|
289
|
-
###
|
|
290
|
-
|
|
291
|
-
# Dev
|
|
385
|
+
### Automated Deployment (GitHub Actions) ⭐
|
|
386
|
+
\`\`\`bash
|
|
387
|
+
# Dev version (develop branch)
|
|
292
388
|
git checkout develop
|
|
293
389
|
npm run version:dev
|
|
294
390
|
git push origin develop --tags
|
|
295
|
-
```
|
|
296
391
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
392
|
+
# Production version (main branch)
|
|
393
|
+
git checkout main
|
|
394
|
+
npm run version:patch # or minor, major
|
|
395
|
+
git push origin main --tags
|
|
396
|
+
\`\`\`
|
|
300
397
|
|
|
301
|
-
|
|
302
|
-
```bash
|
|
303
|
-
# Dev 버전
|
|
304
|
-
npm run version:dev
|
|
305
|
-
npm run publish:dev
|
|
306
|
-
```
|
|
398
|
+
GitHub Actions will automatically build and publish to npm.
|
|
307
399
|
|
|
308
|
-
|
|
400
|
+
### Manual Deployment (Not Recommended)
|
|
401
|
+
\`\`\`bash
|
|
402
|
+
# Dev version
|
|
403
|
+
npm run build
|
|
404
|
+
npm publish --access public --tag dev
|
|
309
405
|
|
|
310
|
-
|
|
406
|
+
# Production version
|
|
407
|
+
npm run build
|
|
408
|
+
npm publish --access public
|
|
409
|
+
\`\`\`
|
|
311
410
|
|
|
312
411
|
---
|
|
313
412
|
|
|
314
|
-
##
|
|
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
|
+
---
|
|
315
420
|
|
|
316
|
-
|
|
317
|
-
- 이슈: [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
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
|
+
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
|
-
*
|
|
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
|
-
import{
|
|
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{
|
|
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";
|
|
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};
|
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-dev.
|
|
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",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};
|
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 };
|
|
File without changes
|