opik-mcp 0.0.1

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.
@@ -0,0 +1,323 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>MCP SSE Client</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
10
+ line-height: 1.6;
11
+ margin: 0;
12
+ padding: 20px;
13
+ color: #333;
14
+ max-width: 1200px;
15
+ margin: 0 auto;
16
+ }
17
+
18
+ h1, h2, h3 {
19
+ color: #0066cc;
20
+ }
21
+
22
+ #messages {
23
+ height: 300px;
24
+ overflow-y: auto;
25
+ border: 1px solid #ddd;
26
+ padding: 10px;
27
+ margin-bottom: 20px;
28
+ background-color: #f9f9f9;
29
+ border-radius: 4px;
30
+ font-family: monospace;
31
+ }
32
+
33
+ #message-form {
34
+ margin-bottom: 20px;
35
+ }
36
+
37
+ textarea {
38
+ width: 100%;
39
+ padding: 10px;
40
+ border: 1px solid #ddd;
41
+ border-radius: 4px;
42
+ min-height: 100px;
43
+ font-family: monospace;
44
+ margin-bottom: 10px;
45
+ }
46
+
47
+ button {
48
+ background-color: #0066cc;
49
+ color: white;
50
+ border: none;
51
+ padding: 10px 20px;
52
+ border-radius: 4px;
53
+ cursor: pointer;
54
+ font-size: 16px;
55
+ }
56
+
57
+ button:hover {
58
+ background-color: #0055aa;
59
+ }
60
+
61
+ .message {
62
+ margin-bottom: 8px;
63
+ border-bottom: 1px solid #eee;
64
+ padding-bottom: 8px;
65
+ }
66
+
67
+ .message-time {
68
+ color: #666;
69
+ font-size: 12px;
70
+ }
71
+
72
+ .message-direction {
73
+ display: inline-block;
74
+ padding: 2px 6px;
75
+ border-radius: 3px;
76
+ font-size: 12px;
77
+ margin-right: 8px;
78
+ }
79
+
80
+ .direction-incoming {
81
+ background-color: #e7f3ff;
82
+ color: #0066cc;
83
+ }
84
+
85
+ .direction-outgoing {
86
+ background-color: #e7ffe7;
87
+ color: #007700;
88
+ }
89
+
90
+ .message-content {
91
+ white-space: pre-wrap;
92
+ background-color: #fff;
93
+ padding: 8px;
94
+ border-radius: 4px;
95
+ overflow-x: auto;
96
+ }
97
+
98
+ .connection-info {
99
+ margin-bottom: 20px;
100
+ padding: 10px;
101
+ background-color: #f0f0f0;
102
+ border-radius: 4px;
103
+ }
104
+
105
+ .status {
106
+ display: inline-block;
107
+ width: 12px;
108
+ height: 12px;
109
+ border-radius: 50%;
110
+ margin-right: 8px;
111
+ }
112
+
113
+ .status-connected {
114
+ background-color: #00cc00;
115
+ }
116
+
117
+ .status-disconnected {
118
+ background-color: #cc0000;
119
+ }
120
+ </style>
121
+ </head>
122
+ <body>
123
+ <h1>MCP SSE Client</h1>
124
+
125
+ <div class="connection-info">
126
+ <div id="connection-status">
127
+ <span class="status status-disconnected"></span> Disconnected
128
+ </div>
129
+ <div>
130
+ <label for="server-url">Server URL:</label>
131
+ <input type="text" id="server-url" value="http://localhost:3001" style="width: 250px;">
132
+ <button id="connect-btn">Connect</button>
133
+ <button id="disconnect-btn" disabled>Disconnect</button>
134
+ </div>
135
+ </div>
136
+
137
+ <h2>Send Message</h2>
138
+ <div id="message-form">
139
+ <textarea id="message-input" placeholder="Enter your JSON message here...">
140
+ {
141
+ "jsonrpc": "2.0",
142
+ "method": "mcp__get_server_info",
143
+ "id": "1",
144
+ "params": {}
145
+ }
146
+ </textarea>
147
+ <button id="send-btn" disabled>Send Message</button>
148
+ </div>
149
+
150
+ <h2>Messages</h2>
151
+ <div id="messages"></div>
152
+
153
+ <script>
154
+ let eventSource = null;
155
+ const messagesContainer = document.getElementById('messages');
156
+ const messageInput = document.getElementById('message-input');
157
+ const sendBtn = document.getElementById('send-btn');
158
+ const connectBtn = document.getElementById('connect-btn');
159
+ const disconnectBtn = document.getElementById('disconnect-btn');
160
+ const serverUrlInput = document.getElementById('server-url');
161
+ const connectionStatus = document.getElementById('connection-status');
162
+
163
+ // Function to connect to the SSE server
164
+ function connect() {
165
+ const serverUrl = serverUrlInput.value.trim();
166
+ if (!serverUrl) {
167
+ alert('Please enter a valid server URL');
168
+ return;
169
+ }
170
+
171
+ try {
172
+ // Generate a unique client ID
173
+ const clientId = 'client_' + Date.now();
174
+
175
+ // Create a new EventSource
176
+ eventSource = new EventSource(`${serverUrl}/events?clientId=${clientId}`);
177
+
178
+ // Listen for messages
179
+ eventSource.onmessage = (event) => {
180
+ const data = event.data;
181
+ addMessage('incoming', data);
182
+ };
183
+
184
+ // Handle connection open
185
+ eventSource.onopen = () => {
186
+ connectionStatus.innerHTML = '<span class="status status-connected"></span> Connected';
187
+ connectBtn.disabled = true;
188
+ disconnectBtn.disabled = false;
189
+ sendBtn.disabled = false;
190
+ addMessage('system', 'Connected to server');
191
+ };
192
+
193
+ // Handle errors
194
+ eventSource.onerror = (error) => {
195
+ console.error('EventSource error:', error);
196
+ addMessage('system', 'Connection error. Reconnecting...');
197
+ connectionStatus.innerHTML = '<span class="status status-disconnected"></span> Error connecting';
198
+ };
199
+ } catch (error) {
200
+ console.error('Error connecting to server:', error);
201
+ addMessage('system', `Error connecting: ${error.message}`);
202
+ }
203
+ }
204
+
205
+ // Function to disconnect from the SSE server
206
+ function disconnect() {
207
+ if (eventSource) {
208
+ eventSource.close();
209
+ eventSource = null;
210
+
211
+ connectionStatus.innerHTML = '<span class="status status-disconnected"></span> Disconnected';
212
+ connectBtn.disabled = false;
213
+ disconnectBtn.disabled = true;
214
+ sendBtn.disabled = true;
215
+
216
+ addMessage('system', 'Disconnected from server');
217
+ }
218
+ }
219
+
220
+ // Function to send a message to the server
221
+ async function sendMessage() {
222
+ const message = messageInput.value.trim();
223
+ if (!message) {
224
+ alert('Please enter a message to send');
225
+ return;
226
+ }
227
+
228
+ try {
229
+ // Parse message to validate JSON
230
+ const jsonMessage = JSON.parse(message);
231
+
232
+ // Send the message to the server
233
+ const serverUrl = serverUrlInput.value.trim();
234
+ const response = await fetch(`${serverUrl}/send`, {
235
+ method: 'POST',
236
+ headers: {
237
+ 'Content-Type': 'application/json',
238
+ },
239
+ body: message,
240
+ });
241
+
242
+ const responseData = await response.json();
243
+
244
+ if (response.ok) {
245
+ addMessage('outgoing', message);
246
+ } else {
247
+ addMessage('system', `Error sending message: ${responseData.message || 'Unknown error'}`);
248
+ }
249
+ } catch (error) {
250
+ console.error('Error sending message:', error);
251
+ addMessage('system', `Error sending message: ${error.message}`);
252
+ }
253
+ }
254
+
255
+ // Function to add a message to the messages container
256
+ function addMessage(direction, content) {
257
+ const messageDiv = document.createElement('div');
258
+ messageDiv.className = 'message';
259
+
260
+ const now = new Date();
261
+ const time = now.toLocaleTimeString();
262
+
263
+ let directionLabel = '';
264
+ let directionClass = '';
265
+
266
+ switch (direction) {
267
+ case 'incoming':
268
+ directionLabel = 'Received';
269
+ directionClass = 'direction-incoming';
270
+ break;
271
+ case 'outgoing':
272
+ directionLabel = 'Sent';
273
+ directionClass = 'direction-outgoing';
274
+ break;
275
+ case 'system':
276
+ directionLabel = 'System';
277
+ directionClass = '';
278
+ break;
279
+ }
280
+
281
+ let contentDisplay = content;
282
+
283
+ // Try to pretty-print JSON if it's not a system message
284
+ if (direction !== 'system') {
285
+ try {
286
+ contentDisplay = JSON.stringify(JSON.parse(content), null, 2);
287
+ } catch (e) {
288
+ // Not valid JSON, just use as-is
289
+ }
290
+ }
291
+
292
+ messageDiv.innerHTML = `
293
+ <div>
294
+ <span class="message-time">${time}</span>
295
+ <span class="message-direction ${directionClass}">${directionLabel}</span>
296
+ </div>
297
+ <div class="message-content">${direction === 'system' ? content : contentDisplay}</div>
298
+ `;
299
+
300
+ messagesContainer.appendChild(messageDiv);
301
+
302
+ // Auto-scroll to bottom
303
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
304
+ }
305
+
306
+ // Set up event listeners
307
+ connectBtn.addEventListener('click', connect);
308
+ disconnectBtn.addEventListener('click', disconnect);
309
+ sendBtn.addEventListener('click', sendMessage);
310
+
311
+ // Allow pressing Enter in the input to send a message
312
+ messageInput.addEventListener('keydown', (event) => {
313
+ if (event.key === 'Enter' && event.ctrlKey) {
314
+ sendMessage();
315
+ event.preventDefault();
316
+ }
317
+ });
318
+
319
+ // Add a welcome message
320
+ addMessage('system', 'Welcome to the MCP SSE Client. Click Connect to start.');
321
+ </script>
322
+ </body>
323
+ </html>
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Configuration loader for Opik MCP server
3
+ * Loads and validates environment variables from .env file
4
+ * and/or command-line arguments
5
+ */
6
+ import yargs from 'yargs';
7
+ import { hideBin } from 'yargs/helpers';
8
+ import * as fs from 'node:fs';
9
+ /**
10
+ * File-based logger
11
+ * Only writes if debug mode is enabled or being set to enabled
12
+ */
13
+ function writeToLogFile(message, forceWrite = false) {
14
+ try {
15
+ // Check if debug mode is enabled or being set to enabled
16
+ // This check is special because we're in the process of parsing args
17
+ const debugArg = process.argv.includes('--debug') &&
18
+ process.argv[process.argv.indexOf('--debug') + 1] === 'true';
19
+ const debugEnv = process.env.DEBUG_MODE === 'true';
20
+ if (debugArg || debugEnv || forceWrite) {
21
+ const logFile = '/tmp/opik-mcp.log';
22
+ if (!fs.existsSync(logFile)) {
23
+ fs.writeFileSync(logFile, `Opik MCP Server Started: ${new Date().toISOString()}\n`);
24
+ }
25
+ fs.appendFileSync(logFile, `[${new Date().toISOString()}] [config] ${message}\n`);
26
+ }
27
+ }
28
+ catch (error) {
29
+ // Silently fail if we can't write to the log file
30
+ }
31
+ }
32
+ /**
33
+ * Parse command-line arguments
34
+ */
35
+ function parseCommandLineArgs() {
36
+ return yargs(hideBin(process.argv))
37
+ // API Configuration
38
+ .option('apiUrl', {
39
+ alias: 'url',
40
+ type: 'string',
41
+ description: 'API base URL',
42
+ })
43
+ .option('apiKey', {
44
+ alias: 'key',
45
+ type: 'string',
46
+ description: 'API key for authentication',
47
+ })
48
+ .option('workspace', {
49
+ alias: 'ws',
50
+ type: 'string',
51
+ description: 'Workspace name',
52
+ })
53
+ .option('selfHosted', {
54
+ type: 'boolean',
55
+ description: 'Whether the instance is self-hosted',
56
+ })
57
+ .option('debug', {
58
+ type: 'boolean',
59
+ description: 'Enable debug mode',
60
+ })
61
+ // Transport Configuration
62
+ .option('transport', {
63
+ type: 'string',
64
+ description: 'Transport type (stdio or sse)',
65
+ choices: ['stdio', 'sse'],
66
+ default: 'stdio',
67
+ })
68
+ .option('ssePort', {
69
+ type: 'number',
70
+ description: 'Port for SSE transport',
71
+ default: 3001,
72
+ })
73
+ .option('sseHost', {
74
+ type: 'string',
75
+ description: 'Host for SSE transport',
76
+ default: 'localhost',
77
+ })
78
+ .option('sseLogPath', {
79
+ type: 'string',
80
+ description: 'Log file path for SSE transport',
81
+ default: '/tmp/opik-mcp-sse.log',
82
+ })
83
+ // MCP Configuration
84
+ .option('mcpName', {
85
+ type: 'string',
86
+ description: 'MCP server name',
87
+ })
88
+ .option('mcpVersion', {
89
+ type: 'string',
90
+ description: 'MCP server version',
91
+ })
92
+ .option('mcpPort', {
93
+ type: 'number',
94
+ description: 'MCP server port',
95
+ })
96
+ .option('mcpLogging', {
97
+ type: 'boolean',
98
+ description: 'Enable MCP server logging',
99
+ })
100
+ .option('mcpDefaultWorkspace', {
101
+ type: 'string',
102
+ description: 'Default workspace name',
103
+ })
104
+ // Tool enablement
105
+ .option('disablePromptTools', {
106
+ type: 'boolean',
107
+ description: 'Disable prompt-related tools',
108
+ })
109
+ .option('disableProjectTools', {
110
+ type: 'boolean',
111
+ description: 'Disable project-related tools',
112
+ })
113
+ .option('disableTraceTools', {
114
+ type: 'boolean',
115
+ description: 'Disable trace-related tools',
116
+ })
117
+ .option('disableMetricTools', {
118
+ type: 'boolean',
119
+ description: 'Disable metric-related tools',
120
+ })
121
+ .help()
122
+ .parse();
123
+ }
124
+ /**
125
+ * Load environment variables with fallbacks
126
+ */
127
+ function loadConfig() {
128
+ // Parse command-line arguments first
129
+ const args = parseCommandLineArgs();
130
+ // Try to load from process.env and command-line args, with command-line taking precedence
131
+ const config = {
132
+ // API configuration with fallbacks - with much more forgiving defaults
133
+ apiBaseUrl: args.apiUrl || process.env.OPIK_API_BASE_URL || 'https://www.comet.com/opik/api',
134
+ workspaceName: (args.workspace || process.env.OPIK_WORKSPACE_NAME || 'default').replace(/^['"](.*)['"]$/, '$1'), // Remove any quotes
135
+ apiKey: args.apiKey || process.env.OPIK_API_KEY || '',
136
+ isSelfHosted: args.selfHosted !== undefined
137
+ ? args.selfHosted
138
+ : process.env.OPIK_SELF_HOSTED === 'true' || false,
139
+ debugMode: args.debug !== undefined ? args.debug : process.env.DEBUG_MODE === 'true' || false,
140
+ // Transport configuration
141
+ transport: (args.transport || process.env.TRANSPORT || 'stdio'),
142
+ ssePort: args.ssePort || (process.env.SSE_PORT ? parseInt(process.env.SSE_PORT, 10) : 3001),
143
+ sseHost: args.sseHost || process.env.SSE_HOST || 'localhost',
144
+ sseLogPath: args.sseLogPath || process.env.SSE_LOG_PATH || '/tmp/opik-mcp-sse.log',
145
+ // MCP configuration with fallbacks
146
+ mcpName: args.mcpName || process.env.MCP_NAME || 'opik-manager',
147
+ mcpVersion: args.mcpVersion || process.env.MCP_VERSION || '1.0.0',
148
+ mcpPort: args.mcpPort || (process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : undefined),
149
+ mcpLogging: args.mcpLogging !== undefined ? args.mcpLogging : process.env.MCP_LOGGING === 'true' || false,
150
+ mcpDefaultWorkspace: args.mcpDefaultWorkspace || process.env.MCP_DEFAULT_WORKSPACE || 'default',
151
+ // Tool enablement with fallbacks - note the logic reversal for the command-line args
152
+ mcpEnablePromptTools: args.disablePromptTools
153
+ ? false
154
+ : process.env.MCP_ENABLE_PROMPT_TOOLS !== 'false', // Enable by default
155
+ mcpEnableProjectTools: args.disableProjectTools
156
+ ? false
157
+ : process.env.MCP_ENABLE_PROJECT_TOOLS !== 'false', // Enable by default
158
+ mcpEnableTraceTools: args.disableTraceTools
159
+ ? false
160
+ : process.env.MCP_ENABLE_TRACE_TOOLS !== 'false', // Enable by default
161
+ mcpEnableMetricTools: args.disableMetricTools
162
+ ? false
163
+ : process.env.MCP_ENABLE_METRIC_TOOLS !== 'false', // Enable by default
164
+ };
165
+ // Validate required fields but be much more forgiving
166
+ if (!config.apiKey) {
167
+ // Only warn about missing API key, don't throw an error
168
+ writeToLogFile(`Warning: No API key provided - some functionality will be limited`, true);
169
+ // Still allow the server to start even without an API key
170
+ }
171
+ // Log configuration if in debug mode
172
+ if (config.debugMode) {
173
+ writeToLogFile('Opik MCP Configuration:');
174
+ writeToLogFile(`- API Base URL: ${config.apiBaseUrl}`);
175
+ writeToLogFile(`- Self-hosted: ${config.isSelfHosted ? 'Yes' : 'No'}`);
176
+ if (!config.isSelfHosted) {
177
+ writeToLogFile(`- Workspace: ${config.workspaceName}`);
178
+ }
179
+ writeToLogFile(`- Debug mode: ${config.debugMode ? 'Enabled' : 'Disabled'}`);
180
+ // Log transport configuration
181
+ writeToLogFile('\nTransport Configuration:');
182
+ writeToLogFile(`- Transport: ${config.transport}`);
183
+ if (config.transport === 'sse') {
184
+ writeToLogFile(`- SSE Port: ${config.ssePort}`);
185
+ writeToLogFile(`- SSE Host: ${config.sseHost}`);
186
+ writeToLogFile(`- SSE Log Path: ${config.sseLogPath}`);
187
+ }
188
+ // Log MCP configuration
189
+ writeToLogFile('\nMCP Configuration:');
190
+ writeToLogFile(`- MCP Name: ${config.mcpName}`);
191
+ writeToLogFile(`- MCP Version: ${config.mcpVersion}`);
192
+ if (config.mcpPort)
193
+ writeToLogFile(`- MCP Port: ${config.mcpPort}`);
194
+ writeToLogFile(`- MCP Logging: ${config.mcpLogging ? 'Enabled' : 'Disabled'}`);
195
+ writeToLogFile(`- MCP Default Workspace: ${config.mcpDefaultWorkspace}`);
196
+ writeToLogFile(`- Prompt Tools: ${config.mcpEnablePromptTools ? 'Enabled' : 'Disabled'}`);
197
+ writeToLogFile(`- Project Tools: ${config.mcpEnableProjectTools ? 'Enabled' : 'Disabled'}`);
198
+ writeToLogFile(`- Trace Tools: ${config.mcpEnableTraceTools ? 'Enabled' : 'Disabled'}`);
199
+ writeToLogFile(`- Metric Tools: ${config.mcpEnableMetricTools ? 'Enabled' : 'Disabled'}`);
200
+ }
201
+ return config;
202
+ }
203
+ // Export the configuration
204
+ const config = loadConfig();
205
+ export default config;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Debug logging utility for MCP server
3
+ */
4
+ // Write directly to a file for debugging
5
+ import fs from 'fs';
6
+ export function initDebugLog() {
7
+ const logPath = '/tmp/opik-mcp-debug.log';
8
+ // Clear previous log
9
+ try {
10
+ fs.writeFileSync(logPath, 'MCP DEBUG LOG STARTED\n');
11
+ }
12
+ catch (error) {
13
+ // If we can't write to the file, use console.error
14
+ console.error('Failed to write to debug log file:', error);
15
+ }
16
+ // Log startup information
17
+ logDebug('MCP Server starting initialization');
18
+ logDebug(`Process ID: ${process.pid}`);
19
+ logDebug(`Node Version: ${process.version}`);
20
+ logDebug(`Working Directory: ${process.cwd()}`);
21
+ // Log environment variables
22
+ logDebug('Environment Variables:');
23
+ Object.keys(process.env)
24
+ .filter(key => key.startsWith('OPIK_'))
25
+ .forEach(key => {
26
+ let value = process.env[key];
27
+ // Mask sensitive information
28
+ if (key === 'OPIK_API_KEY')
29
+ value = '***MASKED***';
30
+ logDebug(` ${key}: ${value}`);
31
+ });
32
+ // Log command line arguments
33
+ logDebug('Command Line Arguments:');
34
+ process.argv.forEach((arg, index) => {
35
+ logDebug(` ${index}: ${arg}`);
36
+ });
37
+ // Set up uncaught exception handler
38
+ process.on('uncaughtException', error => {
39
+ logDebug(`UNCAUGHT EXCEPTION: ${error.message}`);
40
+ logDebug(error.stack || 'No stack trace available');
41
+ });
42
+ // Set up unhandled rejection handler
43
+ process.on('unhandledRejection', (reason, _promise) => {
44
+ logDebug(`UNHANDLED REJECTION: ${reason}`);
45
+ });
46
+ // Set up exit handler
47
+ process.on('exit', code => {
48
+ logDebug(`Process exiting with code: ${code}`);
49
+ });
50
+ }
51
+ export function logDebug(message) {
52
+ const logPath = '/tmp/opik-mcp-debug.log';
53
+ const timestamp = new Date().toISOString();
54
+ const logMessage = `[${timestamp}] ${message}\n`;
55
+ return new Promise(resolve => {
56
+ try {
57
+ fs.appendFileSync(logPath, logMessage);
58
+ }
59
+ catch (error) {
60
+ console.error('Failed to append to debug log file:', error);
61
+ }
62
+ resolve();
63
+ });
64
+ }