@awarecorp/mcp-logger 0.0.2-dev.5 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -327
- package/dist/cli/interceptor.js +1 -1
- package/dist/cli/main.js +1 -1
- package/dist/cli/pairing.js +1 -1
- package/dist/core/config.js +1 -1
- package/dist/core/span-builder.js +1 -1
- package/dist/core/span-utils.js +1 -1
- package/dist/core/telemetry.js +1 -1
- package/dist/index.js +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/instrumentation.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,421 +1,224 @@
|
|
|
1
1
|
# @awarecorp/mcp-logger
|
|
2
2
|
|
|
3
|
-
**Unified MCP Observability Solution** -
|
|
3
|
+
**Unified MCP Observability Solution** - Monitor your Model Context Protocol servers with zero configuration.
|
|
4
4
|
|
|
5
|
-
[
|
|
5
|
+
[](https://www.npmjs.com/package/@awarecorp/mcp-logger)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
[Aware Corp](https://awarecorp.io/)
|
|
6
9
|
|
|
7
10
|
---
|
|
8
11
|
|
|
9
|
-
## 🎯
|
|
12
|
+
## 🎯 Overview
|
|
13
|
+
|
|
14
|
+
`@awarecorp/mcp-logger` provides comprehensive observability for MCP servers in two ways:
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
- **SDK**: Direct integration for custom MCP servers
|
|
17
|
+
- **CLI**: Zero-code wrapper for existing MCP packages
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
If you're using an existing MCP server (npm package), use the CLI.
|
|
19
|
+
All telemetry data is sent to OpenTelemetry-compatible endpoints with automatic request-response pairing.
|
|
16
20
|
|
|
17
21
|
---
|
|
18
22
|
|
|
19
23
|
## 📦 Installation
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
```bash
|
|
22
26
|
npm install @awarecorp/mcp-logger
|
|
23
|
-
|
|
27
|
+
```
|
|
24
28
|
|
|
25
29
|
---
|
|
26
30
|
|
|
27
|
-
##
|
|
31
|
+
## 🚀 Quick Start
|
|
28
32
|
|
|
29
|
-
###
|
|
33
|
+
### SDK Mode
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
33
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
34
|
-
import { trace } from '@awarecorp/mcp-logger';
|
|
35
|
+
Instrument your MCP server with one line of code:
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{ capabilities: { tools: {} } }
|
|
39
|
-
);
|
|
37
|
+
```typescript
|
|
38
|
+
import { trace } from '@awarecorp/mcp-logger';
|
|
40
39
|
|
|
41
|
-
// 🔥 Enable auto-instrumentation (just add this!)
|
|
42
40
|
trace(server, {
|
|
43
|
-
apiKey:
|
|
44
|
-
serviceName: 'my-
|
|
45
|
-
debug: true, // Enable debug logs (optional)
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Register handlers as usual
|
|
49
|
-
server.setRequestHandler('tools/list', async () => ({
|
|
50
|
-
tools: [
|
|
51
|
-
{ name: 'read_file', description: 'Read a file' },
|
|
52
|
-
{ name: 'write_file', description: 'Write to a file' },
|
|
53
|
-
],
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
server.setRequestHandler('tools/call', async (request) => {
|
|
57
|
-
const { name, arguments: args } = request.params;
|
|
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 };
|
|
41
|
+
apiKey: 'YOUR_API_KEY',
|
|
42
|
+
serviceName: 'my-mcp-server'
|
|
68
43
|
});
|
|
44
|
+
```
|
|
69
45
|
|
|
70
|
-
|
|
71
|
-
const transport = new StdioServerTransport();
|
|
72
|
-
await server.connect(transport);
|
|
73
|
-
\`\`\`
|
|
46
|
+
### CLI Mode
|
|
74
47
|
|
|
75
|
-
|
|
48
|
+
Wrap any existing MCP server without code changes:
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
50
|
+
```bash
|
|
51
|
+
npx @awarecorp/mcp-logger \
|
|
52
|
+
-k YOUR_API_KEY \
|
|
53
|
+
-s my-server \
|
|
54
|
+
npx your-mcp-server
|
|
55
|
+
```
|
|
81
56
|
|
|
82
|
-
|
|
83
|
-
{ name: 'my-server', version: '1.0.0' },
|
|
84
|
-
{ capabilities: { tools: {} } }
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
// 🔥 Enable auto-instrumentation
|
|
88
|
-
trace(server, {
|
|
89
|
-
apiKey: process.env.AWARE_API_KEY,
|
|
90
|
-
serviceName: 'my-mcp-server',
|
|
91
|
-
});
|
|
57
|
+
---
|
|
92
58
|
|
|
93
|
-
|
|
94
|
-
tools: [{ name: 'test', description: 'Test tool' }],
|
|
95
|
-
}));
|
|
59
|
+
## 📋 Features
|
|
96
60
|
|
|
97
|
-
|
|
98
|
-
// Add custom logs
|
|
99
|
-
trace.addLog({ level: 'info', message: 'Tool called' });
|
|
100
|
-
|
|
101
|
-
return { result: 'success' };
|
|
102
|
-
});
|
|
61
|
+
### Core Capabilities
|
|
103
62
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
63
|
+
- **Automatic Request-Response Pairing**: Single span per request-response cycle
|
|
64
|
+
- **Selective Method Tracking**: Only tracks `tools/call` and `tools/list` for efficiency
|
|
65
|
+
- **Custom Event Logging**: Add context-aware logs within request handlers
|
|
66
|
+
- **Transport Support**: stdio and SSE (Server-Sent Events)
|
|
67
|
+
- **Zero Configuration**: Works out of the box with sensible defaults
|
|
107
68
|
|
|
108
|
-
###
|
|
69
|
+
### Telemetry Data
|
|
109
70
|
|
|
110
|
-
|
|
71
|
+
All spans include:
|
|
111
72
|
|
|
112
|
-
|
|
73
|
+
- Request/response timestamps and payloads
|
|
74
|
+
- Method name and parameters
|
|
75
|
+
- Tool names and arguments (for `tools/call`)
|
|
76
|
+
- Custom events and logs
|
|
77
|
+
- Error details (if any)
|
|
78
|
+
- Duration metrics
|
|
79
|
+
- CLI server information (command, args, env)
|
|
113
80
|
|
|
114
|
-
|
|
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
|
|
81
|
+
### Data Format
|
|
121
82
|
|
|
122
|
-
|
|
83
|
+
Optimized for Elasticsearch with flat structure:
|
|
84
|
+
- `mcp.method`: Method name
|
|
85
|
+
- `mcp.source`: `sdk` or `cli`
|
|
86
|
+
- `mcp.transport`: `stdio` or `sse`
|
|
87
|
+
- `mcp.duration_ms`: Request duration
|
|
88
|
+
- `mcp.tool.name`: Tool name (tools/call only)
|
|
89
|
+
- `mcp.events`: Custom log entries (JSON array)
|
|
90
|
+
- `mcp.cli.server`: CLI execution info (JSON object)
|
|
123
91
|
|
|
124
92
|
---
|
|
125
93
|
|
|
126
|
-
|
|
94
|
+
## 🔧 SDK API
|
|
95
|
+
|
|
96
|
+
### `trace(server, options)`
|
|
127
97
|
|
|
128
|
-
|
|
98
|
+
Enable automatic instrumentation on an MCP server.
|
|
129
99
|
|
|
130
100
|
**Parameters:**
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
\`\`\`
|
|
101
|
+
- `server`: MCP Server instance
|
|
102
|
+
- `options.apiKey`: API key for authentication (required)
|
|
103
|
+
- `options.serviceName`: Service identifier (optional, auto-generated if omitted)
|
|
104
|
+
- `options.endpoint`: Custom OTLP endpoint (optional)
|
|
105
|
+
- `options.debug`: Enable debug logging (optional, default: false)
|
|
154
106
|
|
|
155
|
-
|
|
107
|
+
**Returns:** `void`
|
|
156
108
|
|
|
157
|
-
|
|
109
|
+
### `trace.addLog(entry)`
|
|
158
110
|
|
|
159
|
-
|
|
111
|
+
Add custom log entries within request handlers.
|
|
160
112
|
|
|
161
|
-
**
|
|
113
|
+
**Parameters:**
|
|
114
|
+
- `entry.level`: Log level (`info` | `warn` | `error`)
|
|
115
|
+
- `entry.message`: Log message (required)
|
|
116
|
+
- `entry.metadata`: Additional data (optional)
|
|
117
|
+
- `entry.timestamp`: Unix timestamp in milliseconds (optional, auto-generated)
|
|
162
118
|
|
|
163
|
-
**
|
|
164
|
-
\`\`\`typescript
|
|
165
|
-
process.on('SIGTERM', async () => {
|
|
166
|
-
await trace.shutdown();
|
|
167
|
-
process.exit(0);
|
|
168
|
-
});
|
|
169
|
-
\`\`\`
|
|
119
|
+
**Returns:** `void`
|
|
170
120
|
|
|
171
|
-
|
|
121
|
+
### `trace.shutdown()`
|
|
172
122
|
|
|
173
|
-
|
|
123
|
+
Gracefully shutdown telemetry and flush pending data.
|
|
174
124
|
|
|
175
|
-
|
|
125
|
+
**Returns:** `Promise<void>`
|
|
176
126
|
|
|
177
|
-
|
|
178
|
-
npx @awarecorp/mcp-logger \\
|
|
179
|
-
--api-key YOUR_API_KEY \\
|
|
180
|
-
-- \\
|
|
181
|
-
npx @modelcontextprotocol/server-filesystem /path/to/dir
|
|
182
|
-
\`\`\`
|
|
127
|
+
---
|
|
183
128
|
|
|
184
|
-
|
|
129
|
+
## 🖥️ CLI Usage
|
|
185
130
|
|
|
186
|
-
###
|
|
131
|
+
### Basic Syntax
|
|
187
132
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
133
|
+
```bash
|
|
134
|
+
mcp-logger [options] <command...>
|
|
135
|
+
```
|
|
191
136
|
|
|
192
|
-
|
|
193
|
-
\`\`\`bash
|
|
194
|
-
mcp-logger --api-key YOUR_KEY -- npx server-filesystem /path
|
|
195
|
-
\`\`\`
|
|
137
|
+
### Options
|
|
196
138
|
|
|
197
|
-
|
|
139
|
+
- `-k, --api-key <key>`: API key (required)
|
|
140
|
+
- `-s, --service-name <name>`: Service name (optional)
|
|
141
|
+
- `-e, --endpoint <url>`: Custom OTLP endpoint (optional)
|
|
198
142
|
|
|
199
|
-
|
|
200
|
-
\`\`\`json
|
|
201
|
-
{
|
|
202
|
-
"mcpServers": {
|
|
203
|
-
"filesystem": {
|
|
204
|
-
"command": "npx",
|
|
205
|
-
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Documents"]
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
\`\`\`
|
|
143
|
+
### Examples
|
|
210
144
|
|
|
211
|
-
|
|
212
|
-
|
|
145
|
+
**Wrap an npx command:**
|
|
146
|
+
```bash
|
|
147
|
+
mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesystem /path
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Use with Claude Desktop:**
|
|
151
|
+
```json
|
|
213
152
|
{
|
|
214
153
|
"mcpServers": {
|
|
215
|
-
"
|
|
154
|
+
"my-server": {
|
|
216
155
|
"command": "npx",
|
|
217
156
|
"args": [
|
|
218
157
|
"-y",
|
|
219
158
|
"@awarecorp/mcp-logger",
|
|
220
|
-
"
|
|
221
|
-
"
|
|
159
|
+
"-k",
|
|
160
|
+
"YOUR_API_KEY",
|
|
161
|
+
"-s",
|
|
162
|
+
"my-server",
|
|
222
163
|
"--",
|
|
223
|
-
"
|
|
224
|
-
"
|
|
164
|
+
"npx",
|
|
165
|
+
"-y",
|
|
166
|
+
"your-mcp-server"
|
|
225
167
|
]
|
|
226
168
|
}
|
|
227
169
|
}
|
|
228
170
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
**That's it!** All requests/responses are now logged to the Aware platform without code changes.
|
|
232
|
-
|
|
233
|
-
### CLI Options
|
|
234
|
-
|
|
235
|
-
#### Required Options
|
|
236
|
-
- \`--api-key <key>\` or \`-k <key>\`: Aware API key (required)
|
|
237
|
-
|
|
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
|
|
242
|
-
|
|
243
|
-
### CLI Usage Examples
|
|
244
|
-
|
|
245
|
-
#### Filesystem Server
|
|
246
|
-
\`\`\`bash
|
|
247
|
-
npx @awarecorp/mcp-logger \\
|
|
248
|
-
--api-key abc123 \\
|
|
249
|
-
--service-name my-filesystem \\
|
|
250
|
-
-- \\
|
|
251
|
-
npx -y @modelcontextprotocol/server-filesystem /Users/username/Documents
|
|
252
|
-
\`\`\`
|
|
253
|
-
|
|
254
|
-
#### GitHub Server
|
|
255
|
-
\`\`\`bash
|
|
256
|
-
npx @awarecorp/mcp-logger \\
|
|
257
|
-
-k abc123 \\
|
|
258
|
-
-s github-integration \\
|
|
259
|
-
-- \\
|
|
260
|
-
npx -y @modelcontextprotocol/server-github
|
|
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
|
-
-- \\
|
|
270
|
-
npx -y @modelcontextprotocol/server-brave-search
|
|
271
|
-
\`\`\`
|
|
171
|
+
```
|
|
272
172
|
|
|
273
173
|
---
|
|
274
174
|
|
|
275
|
-
## 🏗️
|
|
276
|
-
|
|
277
|
-
This package consists of three layers:
|
|
278
|
-
|
|
279
|
-
\`\`\`
|
|
280
|
-
@awarecorp/mcp-logger
|
|
281
|
-
├── SDK (Public API)
|
|
282
|
-
│ ├── trace() - Instrument MCP server
|
|
283
|
-
│ ├── trace.addLog() - Add custom logs
|
|
284
|
-
│ └── trace.shutdown() - Shutdown telemetry
|
|
285
|
-
│
|
|
286
|
-
├── CLI (Binary-only access)
|
|
287
|
-
│ ├── main.ts - CLI entry point
|
|
288
|
-
│ ├── interceptor.ts - Stream interception
|
|
289
|
-
│ └── pairing.ts - Request-Response matching
|
|
290
|
-
│
|
|
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
|
|
312
|
-
|
|
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
|
|
175
|
+
## 🏗️ How It Works
|
|
324
176
|
|
|
325
|
-
|
|
177
|
+
### Request-Response Pairing
|
|
326
178
|
|
|
327
|
-
|
|
179
|
+
Creates **one span per request-response pair** for complete transaction context:
|
|
328
180
|
|
|
329
|
-
|
|
181
|
+
1. Request arrives → stored in pending map
|
|
182
|
+
2. Response arrives → matched by ID, single span created
|
|
183
|
+
3. Timeout (30s) → pending requests cleared
|
|
330
184
|
|
|
331
|
-
|
|
332
|
-
npm run build
|
|
333
|
-
\`\`\`
|
|
185
|
+
### Tracked Methods
|
|
334
186
|
|
|
335
|
-
|
|
187
|
+
Only tracks business-critical methods:
|
|
188
|
+
- `tools/call` - Tool executions
|
|
189
|
+
- `tools/list` - Tool discovery
|
|
336
190
|
|
|
337
|
-
|
|
338
|
-
npm test # Run all tests
|
|
339
|
-
npm run test:watch # Watch mode
|
|
340
|
-
\`\`\`
|
|
341
|
-
|
|
342
|
-
### Local Testing
|
|
343
|
-
|
|
344
|
-
#### SDK Testing
|
|
345
|
-
\`\`\`typescript
|
|
346
|
-
// test.ts
|
|
347
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
348
|
-
import { trace } from './src/index.js';
|
|
349
|
-
|
|
350
|
-
const server = new Server({ name: 'test', version: '1.0.0' }, { capabilities: {} });
|
|
351
|
-
trace(server, { apiKey: 'test', debug: true });
|
|
352
|
-
|
|
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
|
|
361
|
-
npm run build
|
|
362
|
-
node bin/mcp-logger.js --help
|
|
363
|
-
\`\`\`
|
|
191
|
+
Protocol methods (`initialize`, `ping`, etc.) are ignored.
|
|
364
192
|
|
|
365
193
|
---
|
|
366
194
|
|
|
367
|
-
##
|
|
195
|
+
## 📊 Telemetry Data Structure
|
|
368
196
|
|
|
369
|
-
|
|
197
|
+
Each span includes:
|
|
198
|
+
- Request/response timestamps and payloads
|
|
199
|
+
- Method name and parameters
|
|
200
|
+
- Tool names and arguments (for `tools/call`)
|
|
201
|
+
- Custom events and logs
|
|
202
|
+
- Error details and duration metrics
|
|
203
|
+
- CLI server information (command, args, env)
|
|
370
204
|
|
|
371
205
|
---
|
|
372
206
|
|
|
373
207
|
## 🤝 Contributing
|
|
374
208
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
See [DEVELOPMENT.md](./DEVELOPMENT.md) for detailed development guidelines.
|
|
209
|
+
For development setup and guidelines, visit [GitHub Repository](https://github.com/awarecorp/mcp-logger).
|
|
378
210
|
|
|
379
211
|
---
|
|
380
212
|
|
|
381
|
-
##
|
|
382
|
-
|
|
383
|
-
**Recommended:** Automated deployment with GitHub Actions
|
|
384
|
-
|
|
385
|
-
### Automated Deployment (GitHub Actions) ⭐
|
|
386
|
-
\`\`\`bash
|
|
387
|
-
# Dev version (develop branch)
|
|
388
|
-
git checkout develop
|
|
389
|
-
npm run version:dev
|
|
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
|
-
\`\`\`
|
|
213
|
+
## 📄 License
|
|
397
214
|
|
|
398
|
-
|
|
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
|
-
\`\`\`
|
|
215
|
+
MIT © [Aware Corp](https://awarecorp.io/)
|
|
410
216
|
|
|
411
217
|
---
|
|
412
218
|
|
|
413
|
-
##
|
|
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
|
-
---
|
|
219
|
+
## 🔗 Links
|
|
420
220
|
|
|
421
|
-
|
|
221
|
+
- [Aware Corp](https://awarecorp.io/)
|
|
222
|
+
- [npm Package](https://www.npmjs.com/package/@awarecorp/mcp-logger)
|
|
223
|
+
- [GitHub Repository](https://github.com/awarecorp/mcp-logger)
|
|
224
|
+
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
package/dist/cli/interceptor.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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(
|
|
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
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
function e(){return h}function r(){return I}async function t(){if(d)try{await d.shutdown(),d=null,h=null,f=!1,I=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,r=1e4){try{const t=JSON.stringify(e);return t.length>r?t.slice(0,r)+"... (truncated)":t}catch(e){return null}}function o(t){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+t.method;o.startActiveSpan(n,e=>{try{const o={"mcp.method":t.method,"mcp.source":t.source,"mcp.transport":t.transport,"mcp.log_type":"request-response","mcp.request.id":t.request.id+"","mcp.request.timestamp":t.request.timestamp,"mcp.request.params":s(t.request.params)||"{}","mcp.request.params.size":JSON.stringify(t.request.params||{}).length,"mcp.response.timestamp":t.response.timestamp,"mcp.duration_ms":t.duration};if("tools/call"===t.method&&t.request.params){const e=t.request.params.name,r=t.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),r){const e=s(r);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==t.response.result){const e=s(t.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(t.response.result).length)}if(t.request.headers){const e=s(t.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(t.request.headers).length)}if("cli"===t.source){const e=r();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(t.customEvents&&t.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(t.customEvents),o["mcp.events.count"]=t.customEvents.length,t.customEvents.forEach(r=>{e.addEvent("custom."+(r.level||"info"),{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp||Date.now()})})),t.response.error){o["mcp.error"]=!0;const r=t.response.error instanceof Error?t.response.error.message:t.response.error+"";o["mcp.error.message"]=r,t.response.error instanceof Error&&e.recordException(t.response.error),e.setStatus({code:u.ERROR,message:r})}else e.setStatus({code:u.OK});!function(e,r){for(const[t,s]of Object.entries(r))null!=s&&e.setAttribute(t,s)}(e,o)}catch(r){console.error("[MCP Logger] Error creating paired span:",r),e.setStatus({code:u.ERROR,message:r+""})}finally{e.end()}})}import{spawn as n}from"child_process";import{program as i}from"commander";import{NodeSDK as c}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as a}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as p}from"@opentelemetry/resources";import{trace as m,SpanStatusCode as u}from"@opentelemetry/api";import{Transform as l}from"stream";const g={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let d=null,h=null,f=!1,I=null;class y extends l{buffer="";direction;pairer;debug;constructor(e,r,t=!1){super(),this.direction=e,this.pairer=r,this.debug=t}_transform(e,r,t){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),t()}catch(e){t(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);this.handleMessage(r)}catch(r){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){"request"===this.direction&&e.method?this.pairer.onRequest(e):"response"!==this.direction||void 0===e.id||e.method||this.pairer.onResponse(e)}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.pairer.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(r){e(r)}}}class S{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(e){var r;r=e.method,g.TRACKED_METHODS.includes(r)?e.id?(this.pendingRequests.set(e.id,{request:e,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${e.method} (ID: ${e.id})`),setTimeout(()=>this.cleanupStale(e.id),this.TIMEOUT)):this.debug&&console.error("[mcp-logger CLI] Request without ID, skipping:",e.method):this.debug&&console.error("[mcp-logger CLI] Skipping method: "+e.method)}onResponse(e){if(!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const r=this.pendingRequests.get(e.id);if(!r)return void(this.debug&&console.error("[mcp-logger CLI] No matching request for response ID: "+e.id));const t=Date.now(),s=t-r.timestamp;this.debug&&console.error(`[mcp-logger CLI] Pairing success: ${r.request.method} (${s}ms)`),this.createPairedSpan(r.request,e,r.timestamp,t,s),this.pendingRequests.delete(e.id)}createPairedSpan(e,r,t,s,n){o({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:t,params:e.params},response:{timestamp:s,result:r.result,error:r.error},duration:n})}cleanupStale(e){const r=this.pendingRequests.get(e);r&&Date.now()-r.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] Request timeout: ${r.request.method} (ID: ${e})`),this.pendingRequests.delete(e))}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}i.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-s, --service-name <name>","Service name for identification").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(e,r)=>{try{const s=!0;!function(e){if(f)return console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),h;const r=e.serviceName||`${g.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||g.ENDPOINT)),console.error("[MCP Logger] Service: "+r),d=new c({resource:new p({"service.name":r,"service.version":g.SDK_VERSION}),traceExporter:new a({url:e.endpoint||g.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),d.start(),h=m.getTracer("mcp-logger",g.SDK_VERSION),f=!0,console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{console.error("[MCP Logger] Shutting down telemetry..."),await t()};process.once("SIGTERM",s),process.once("SIGINT",s)}({...r,debug:s}),console.error("[MCP Logger CLI] Starting MCP server:",e.join(" "));const[o,...i]=e;!function(e,r,t){I={command:e,args:r,env:t}}(o,i,{...process.env});const u=n(o,i,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),l=new S(s),C=new y("request",l,s);process.stdin.pipe(C).pipe(u.stdin);const E=new y("response",l,s);u.stdout.pipe(E).pipe(process.stdout),u.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),l.clear(),await t(),process.exit(1)}),u.on("exit",async(e,r)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),l.clear(),await t(),process.exit(e||0)});let q=!1;const v=async e=>{q||(q=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),u.kill(e),await Promise.race([new Promise(e=>u.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await t(),process.exit(0))};process.on("SIGTERM",()=>v("SIGTERM")),process.on("SIGINT",()=>v("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await t(),process.exit(1)}}),i.parse();
|
package/dist/cli/pairing.js
CHANGED
|
@@ -1 +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(
|
|
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};
|
package/dist/core/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
function t(t){return E.TRACKED_METHODS.includes(t)}const E={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};export{E as CONFIG,t as shouldTrackMethod};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";
|
|
1
|
+
function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";export{e as createPairedSpan};
|
package/dist/core/span-utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
function t(t,r){for(const[e,n]of Object.entries(r))null!=n&&t.setAttribute(e,n)}function r(t,r,e,n){try{e?t.addEvent(r,e):t.addEvent(r)}catch(t){n&&console.error("[MCP Logger] Failed to add span event:",t)}}function e(t,r,e){const n=r instanceof Error?r.message:"Unknown error",o=r instanceof Error?r.constructor.name:"Error";r instanceof Error&&t.recordException(r),t.setStatus({code:s.ERROR,message:n}),t.setAttribute("error",!0),t.setAttribute("error.type",o),t.setAttribute("error.message",n),e&&console.error("[MCP Logger] Error recorded in span:",r)}function n(t){t.setStatus({code:s.OK})}function o(t,r=1e4){try{const e=JSON.stringify(t);return e.length>r?e.slice(0,r)+"... (truncated)":e}catch(t){return null}}function c(){const t={},r=a.active(),e=i.getSpan(r);if(e){const r=e.spanContext();r&&(t.traceparent=`00-${r.traceId}-${r.spanId}-01`)}return t}import{SpanStatusCode as s,context as a,trace as i}from"@opentelemetry/api";export{r as addSpanEvent,c as getTraceHeaders,n as markSpanSuccess,e as recordSpanError,o as safeStringify,t as setSpanAttributes};
|
package/dist/core/telemetry.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
function e(e){if(y)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),d;const r=e.serviceName||`${m}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||u)),console.error("[MCP Logger] Service: "+r)),p=new c({resource:new s({"service.name":r,"service.version":g}),traceExporter:new l({url:e.endpoint||u,headers:{"x-api-key":e.apiKey}})}),p.start(),d=a.getTracer("mcp-logger",g),y=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await i()};return process.once("SIGTERM",o),process.once("SIGINT",o),d}function r(){return d}function o(){return y}function t(e,r,o){f={command:e,args:r,env:o}}function n(){return f}async function i(){if(p)try{await p.shutdown(),p=null,d=null,y=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}import{NodeSDK as c}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as l}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as s}from"@opentelemetry/resources";import{trace as a}from"@opentelemetry/api";const u="https://aware.mcypher.com/v1/traces",g="1.0.0",m="mcp-server";let p=null,d=null,y=!1,f=null;export{n as getCLIServerInfo,r as getTracer,e as initializeTelemetry,o as isInitializedTelemetry,t as setCLIServerInfo,i as shutdownTelemetry};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
function e(){return g}function t(){return f}async function r(){if(u)try{await u.shutdown(),u=null,g=null,d=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(r){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+r.method;o.startActiveSpan(n,e=>{try{const o={"mcp.method":r.method,"mcp.source":r.source,"mcp.transport":r.transport,"mcp.log_type":"request-response","mcp.request.id":r.request.id+"","mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),t){const e=s(t);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(r.customEvents&&r.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(r.customEvents),o["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){o["mcp.error"]=!0;const t=r.response.error instanceof Error?r.response.error.message:r.response.error+"";o["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,o)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as n,SpanStatusCode as a,context as c}from"@opentelemetry/api";import{NodeSDK as i}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";const p={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let u=null,g=null,d=!1,f=null;const h=Symbol("mcp-logger.customEvents"),E=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(d)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),g;const t=e.serviceName||`${p.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||p.ENDPOINT)),console.error("[MCP Logger] Service: "+t)),u=new i({resource:new l({"service.name":t,"service.version":p.SDK_VERSION}),traceExporter:new m({url:e.endpoint||p.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),u.start(),g=n.getTracer("mcp-logger",p.SDK_VERSION),d=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",s),process.once("SIGINT",s)}(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const n=e.shape?.method,a=n?._def?.value||"unknown";return function(e){return p.TRACKED_METHODS.includes(e)}(a)?(t.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),r(e,async(e,r)=>{const n=Date.now(),i=`${a}-${n}-${Math.random().toString(36).slice(2,8)}`,m=[],l=c.active().setValue(h,m);try{const p=await c.with(l,async()=>await s(e,r)),u=Date.now();return o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:u,result:p},duration:u-n,customEvents:m.length>0?m:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${u-n}ms):`,{method:a,customEvents:m.length}),p}catch(r){const s=Date.now();throw o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:m.length>0?m:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})):(t.debug&&console.log("[mcp-logger] Skipping instrumentation for method: "+a),r(e,s))},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=c.active().getValue(h);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=n.getSpan(c.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{E as trace};
|
package/dist/sdk/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
function e(){return g}function t(){return f}async function r(){if(u)try{await u.shutdown(),u=null,g=null,d=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(r){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+r.method;o.startActiveSpan(n,e=>{try{const o={"mcp.method":r.method,"mcp.source":r.source,"mcp.transport":r.transport,"mcp.log_type":"request-response","mcp.request.id":r.request.id+"","mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),t){const e=s(t);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(r.customEvents&&r.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(r.customEvents),o["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){o["mcp.error"]=!0;const t=r.response.error instanceof Error?r.response.error.message:r.response.error+"";o["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,o)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as n,SpanStatusCode as a,context as c}from"@opentelemetry/api";import{NodeSDK as i}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";const p={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let u=null,g=null,d=!1,f=null;const h=Symbol("mcp-logger.customEvents"),E=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(d)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),g;const t=e.serviceName||`${p.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||p.ENDPOINT)),console.error("[MCP Logger] Service: "+t)),u=new i({resource:new l({"service.name":t,"service.version":p.SDK_VERSION}),traceExporter:new m({url:e.endpoint||p.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),u.start(),g=n.getTracer("mcp-logger",p.SDK_VERSION),d=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",s),process.once("SIGINT",s)}(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const n=e.shape?.method,a=n?._def?.value||"unknown";return function(e){return p.TRACKED_METHODS.includes(e)}(a)?(t.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),r(e,async(e,r)=>{const n=Date.now(),i=`${a}-${n}-${Math.random().toString(36).slice(2,8)}`,m=[],l=c.active().setValue(h,m);try{const p=await c.with(l,async()=>await s(e,r)),u=Date.now();return o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:u,result:p},duration:u-n,customEvents:m.length>0?m:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${u-n}ms):`,{method:a,customEvents:m.length}),p}catch(r){const s=Date.now();throw o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:m.length>0?m:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})):(t.debug&&console.log("[mcp-logger] Skipping instrumentation for method: "+a),r(e,s))},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=c.active().getValue(h);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=n.getSpan(c.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{E as trace};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function t(t,o){const l=t.setRequestHandler.bind(t);t.setRequestHandler=function(t,s){const a=t.shape?.method,i=a?._def?.value||"unknown";return function(e){return r.TRACKED_METHODS.includes(e)}(i)?(o.debug&&console.log("[mcp-logger] Instrumenting handler: "+i),l(t,async(t,r)=>{const l=Date.now(),a=(Math.random().toString(36).slice(2,8),[]),m=n.active().setValue(c,a);try{const c=await n.with(m,async()=>await s(t,r)),u=Date.now();return e(t.params),o.debug&&console.log(`[mcp-logger] Request completed (${u-l}ms):`,{method:i,customEvents:a.length}),c}catch(n){throw e(t.params),o.debug&&console.error("[mcp-logger] Request failed:",n),n}})):(o.debug&&console.log("[mcp-logger] Skipping instrumentation for method: "+i),l(t,s))},o.debug&&console.log("[mcp-logger] Server instrumentation complete")}function o(){return n.active().getValue(c)}import{context as n}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const r={TRACKED_METHODS:["tools/call","tools/list"]},c=Symbol("mcp-logger.customEvents");export{o as getCustomEventsFromContext,t as instrumentMCPServer};
|
package/package.json
CHANGED