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.
- package/LICENSE +203 -0
- package/README.md +203 -0
- package/build/cli.js +72 -0
- package/build/client/index.html +323 -0
- package/build/config.js +205 -0
- package/build/debug-log.js +64 -0
- package/build/index.js +1847 -0
- package/build/mcp-server.js +96 -0
- package/build/test-client.js +436 -0
- package/build/transports/sse-transport.js +169 -0
- package/build/transports/types.js +4 -0
- package/build/types.js +4 -0
- package/build/utils/capabilities.js +303 -0
- package/build/utils/env.js +52 -0
- package/build/utils/examples.js +414 -0
- package/build/utils/metrics-info.js +263 -0
- package/build/utils/tracing-info.js +119 -0
- package/package.json +79 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import cors from 'cors';
|
|
5
|
+
// Setup file-based logging
|
|
6
|
+
const logFile = '/tmp/opik-mcp-sse.log';
|
|
7
|
+
function logToFile(message) {
|
|
8
|
+
try {
|
|
9
|
+
const timestamp = new Date().toISOString();
|
|
10
|
+
fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
// Silently fail if we can't write to the log file
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* SSE (Server-Sent Events) transport for the MCP server
|
|
18
|
+
* This allows the server to be accessed over a network connection
|
|
19
|
+
* with a simple unidirectional streaming protocol
|
|
20
|
+
*/
|
|
21
|
+
export class SSEServerTransport {
|
|
22
|
+
app = express();
|
|
23
|
+
server = null;
|
|
24
|
+
port;
|
|
25
|
+
clients = new Map();
|
|
26
|
+
started = false;
|
|
27
|
+
onclose;
|
|
28
|
+
onerror;
|
|
29
|
+
onmessage;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.port = options.port || 3001;
|
|
32
|
+
// Setup Express server
|
|
33
|
+
this.app.use(cors());
|
|
34
|
+
this.app.use(express.json());
|
|
35
|
+
// Add health check endpoint
|
|
36
|
+
this.app.get('/health', (req, res) => {
|
|
37
|
+
const response = { status: 'ok' };
|
|
38
|
+
res.json(response);
|
|
39
|
+
});
|
|
40
|
+
// SSE endpoint for receiving MCP messages
|
|
41
|
+
this.app.get('/events', (req, res) => {
|
|
42
|
+
const clientId = req.query.clientId || Date.now().toString();
|
|
43
|
+
// Set headers for SSE
|
|
44
|
+
res.writeHead(200, {
|
|
45
|
+
'Content-Type': 'text/event-stream',
|
|
46
|
+
'Cache-Control': 'no-cache',
|
|
47
|
+
Connection: 'keep-alive',
|
|
48
|
+
});
|
|
49
|
+
// Send a welcome message
|
|
50
|
+
res.write(`data: ${JSON.stringify({ type: 'connection', clientId })}\n\n`);
|
|
51
|
+
// Add client to the list
|
|
52
|
+
this.clients.set(clientId, res);
|
|
53
|
+
logToFile(`SSE client connected: ${clientId}`);
|
|
54
|
+
// Handle client disconnect
|
|
55
|
+
req.on('close', () => {
|
|
56
|
+
this.clients.delete(clientId);
|
|
57
|
+
logToFile(`SSE client disconnected: ${clientId}`);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
// Endpoint for sending messages to the MCP server
|
|
61
|
+
this.app.post('/send', (req, res) => {
|
|
62
|
+
const message = req.body;
|
|
63
|
+
if (this.onmessage) {
|
|
64
|
+
try {
|
|
65
|
+
// Forward the message to the MCP connection handler
|
|
66
|
+
this.onmessage(message);
|
|
67
|
+
const response = { status: 'success' };
|
|
68
|
+
res.status(200).json(response);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logToFile(`Error handling message: ${error}`);
|
|
72
|
+
if (this.onerror) {
|
|
73
|
+
this.onerror(error instanceof Error ? error : new Error(String(error)));
|
|
74
|
+
}
|
|
75
|
+
const errorResponse = {
|
|
76
|
+
status: 'error',
|
|
77
|
+
message: String(error),
|
|
78
|
+
};
|
|
79
|
+
res.status(500).json(errorResponse);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const errorResponse = {
|
|
84
|
+
status: 'error',
|
|
85
|
+
message: 'Server not ready',
|
|
86
|
+
};
|
|
87
|
+
res.status(503).json(errorResponse);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Create HTTP server
|
|
91
|
+
this.server = http.createServer(this.app);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Start listening for connections
|
|
95
|
+
*/
|
|
96
|
+
async start() {
|
|
97
|
+
if (this.started) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.started = true;
|
|
101
|
+
return new Promise(resolve => {
|
|
102
|
+
if (!this.server) {
|
|
103
|
+
this.server = http.createServer(this.app);
|
|
104
|
+
}
|
|
105
|
+
this.server.listen(this.port, () => {
|
|
106
|
+
logToFile(`SSE transport listening on port ${this.port}`);
|
|
107
|
+
resolve();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Send a message to all connected clients
|
|
113
|
+
*/
|
|
114
|
+
async send(message) {
|
|
115
|
+
const messageStr = JSON.stringify(message);
|
|
116
|
+
// Broadcast the message to all connected clients
|
|
117
|
+
for (const [clientId, client] of this.clients.entries()) {
|
|
118
|
+
try {
|
|
119
|
+
client.write(`data: ${messageStr}\n\n`);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
logToFile(`Error sending message to client ${clientId}: ${error}`);
|
|
123
|
+
// Remove client if we can't send messages to it
|
|
124
|
+
this.clients.delete(clientId);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Close the transport
|
|
130
|
+
*/
|
|
131
|
+
async close() {
|
|
132
|
+
if (!this.started) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.started = false;
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
// Close all SSE connections
|
|
138
|
+
for (const [clientId, client] of this.clients.entries()) {
|
|
139
|
+
try {
|
|
140
|
+
client.end();
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
logToFile(`Error closing connection to client ${clientId}: ${error}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Clear the clients map
|
|
147
|
+
this.clients.clear();
|
|
148
|
+
// Close the HTTP server
|
|
149
|
+
if (this.server) {
|
|
150
|
+
this.server.close(err => {
|
|
151
|
+
if (err) {
|
|
152
|
+
logToFile(`Error closing SSE server: ${err}`);
|
|
153
|
+
reject(err);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
logToFile('SSE transport stopped');
|
|
157
|
+
if (this.onclose)
|
|
158
|
+
this.onclose();
|
|
159
|
+
resolve();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
this.server = null;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
resolve();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
package/build/types.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opik Comet API Capabilities
|
|
3
|
+
*
|
|
4
|
+
* This file defines the capabilities of the Opik Comet API to provide
|
|
5
|
+
* better context to the MCP about what it can and cannot do.
|
|
6
|
+
* Based on official Opik documentation: https://www.comet.com/docs/opik/
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Detailed capabilities of the Opik Comet API
|
|
10
|
+
* Based on official documentation: https://www.comet.com/docs/opik/
|
|
11
|
+
*/
|
|
12
|
+
export const opikCapabilities = {
|
|
13
|
+
prompts: {
|
|
14
|
+
available: true,
|
|
15
|
+
features: [
|
|
16
|
+
'Create and manage prompt templates',
|
|
17
|
+
'Version control for prompts',
|
|
18
|
+
'Prompt playground for testing different prompts and models',
|
|
19
|
+
'Managing prompts in code with the Python SDK',
|
|
20
|
+
'Retrieve prompt history',
|
|
21
|
+
'Update existing prompts',
|
|
22
|
+
'Delete prompts',
|
|
23
|
+
],
|
|
24
|
+
limitations: [
|
|
25
|
+
'Limited prompt template variables support',
|
|
26
|
+
'No automatic prompt optimization',
|
|
27
|
+
'No A/B testing capabilities built-in',
|
|
28
|
+
'No automatic prompt performance metrics',
|
|
29
|
+
],
|
|
30
|
+
examples: [
|
|
31
|
+
'Creating a prompt template for a specific use case',
|
|
32
|
+
'Versioning prompts to track changes over time',
|
|
33
|
+
'Testing different prompt variations in the playground',
|
|
34
|
+
'Managing prompts programmatically with the SDK',
|
|
35
|
+
],
|
|
36
|
+
versionControl: true,
|
|
37
|
+
templateFormat: 'String with variable placeholders using {{variable}} syntax',
|
|
38
|
+
schema: {
|
|
39
|
+
prompt: {
|
|
40
|
+
id: 'string',
|
|
41
|
+
name: 'string',
|
|
42
|
+
description: 'string',
|
|
43
|
+
created_at: 'timestamp',
|
|
44
|
+
created_by: 'string',
|
|
45
|
+
last_updated_at: 'timestamp',
|
|
46
|
+
last_updated_by: 'string',
|
|
47
|
+
version_count: 'number',
|
|
48
|
+
},
|
|
49
|
+
promptVersion: {
|
|
50
|
+
id: 'string',
|
|
51
|
+
prompt_id: 'string',
|
|
52
|
+
version: 'number',
|
|
53
|
+
template: 'string',
|
|
54
|
+
created_at: 'timestamp',
|
|
55
|
+
created_by: 'string',
|
|
56
|
+
commit_message: 'string',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
projects: {
|
|
61
|
+
available: true,
|
|
62
|
+
features: [
|
|
63
|
+
'Create and manage projects/workspaces',
|
|
64
|
+
'Organize traces by project',
|
|
65
|
+
'Project-level metrics and statistics',
|
|
66
|
+
'Update project metadata',
|
|
67
|
+
'Delete projects',
|
|
68
|
+
'Monitoring dashboards for projects',
|
|
69
|
+
],
|
|
70
|
+
limitations: [
|
|
71
|
+
'No nested project hierarchies',
|
|
72
|
+
'Limited project sharing or collaboration features',
|
|
73
|
+
'No project templates or cloning',
|
|
74
|
+
'Limited metadata customization',
|
|
75
|
+
],
|
|
76
|
+
examples: [
|
|
77
|
+
'Creating a project for a specific AI application',
|
|
78
|
+
'Organizing traces by customer or use case',
|
|
79
|
+
'Tracking metrics across different projects',
|
|
80
|
+
'Setting up monitoring dashboards for a project',
|
|
81
|
+
],
|
|
82
|
+
hierarchySupport: false,
|
|
83
|
+
sharingSupport: false,
|
|
84
|
+
schema: {
|
|
85
|
+
project: {
|
|
86
|
+
id: 'string',
|
|
87
|
+
name: 'string',
|
|
88
|
+
description: 'string',
|
|
89
|
+
created_at: 'timestamp',
|
|
90
|
+
created_by: 'string',
|
|
91
|
+
last_updated_at: 'timestamp',
|
|
92
|
+
last_updated_by: 'string',
|
|
93
|
+
workspace: 'string',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
traces: {
|
|
98
|
+
available: true,
|
|
99
|
+
features: [
|
|
100
|
+
'Record and retrieve LLM interactions (traces)',
|
|
101
|
+
'Log conversations and agent interactions',
|
|
102
|
+
'Log multimodal traces',
|
|
103
|
+
'Log distributed traces',
|
|
104
|
+
'Annotate traces with feedback scores',
|
|
105
|
+
'Track token usage and costs',
|
|
106
|
+
'Filter traces by project',
|
|
107
|
+
'Aggregate trace statistics',
|
|
108
|
+
'View trace metadata',
|
|
109
|
+
'Track latency and performance',
|
|
110
|
+
'Export trace data',
|
|
111
|
+
'OpenTelemetry integration',
|
|
112
|
+
],
|
|
113
|
+
limitations: [
|
|
114
|
+
'No real-time trace streaming',
|
|
115
|
+
'Limited search capabilities within trace content',
|
|
116
|
+
'No built-in trace comparison tools',
|
|
117
|
+
'Limited custom trace tagging system',
|
|
118
|
+
],
|
|
119
|
+
examples: [
|
|
120
|
+
'Recording a conversation with an AI assistant',
|
|
121
|
+
'Logging agent interactions in a complex workflow',
|
|
122
|
+
'Tracking distributed traces across multiple services',
|
|
123
|
+
'Annotating traces with feedback scores',
|
|
124
|
+
'Analyzing token usage patterns',
|
|
125
|
+
'Tracking costs across different models',
|
|
126
|
+
],
|
|
127
|
+
dataRetention: 'Configurable, default varies by deployment',
|
|
128
|
+
searchCapabilities: [
|
|
129
|
+
'Filter by project',
|
|
130
|
+
'Filter by date range',
|
|
131
|
+
'Filter by trace name',
|
|
132
|
+
'Filter by trace type',
|
|
133
|
+
'Basic text search in trace names',
|
|
134
|
+
],
|
|
135
|
+
filterOptions: ['project_id', 'project_name', 'start_date', 'end_date', 'name', 'type'],
|
|
136
|
+
schema: {
|
|
137
|
+
trace: {
|
|
138
|
+
id: 'string',
|
|
139
|
+
project_id: 'string',
|
|
140
|
+
name: 'string',
|
|
141
|
+
start_time: 'timestamp',
|
|
142
|
+
end_time: 'timestamp',
|
|
143
|
+
input: 'object',
|
|
144
|
+
output: 'object',
|
|
145
|
+
metadata: 'object',
|
|
146
|
+
usage: {
|
|
147
|
+
completion_tokens: 'number',
|
|
148
|
+
prompt_tokens: 'number',
|
|
149
|
+
total_tokens: 'number',
|
|
150
|
+
},
|
|
151
|
+
created_at: 'timestamp',
|
|
152
|
+
last_updated_at: 'timestamp',
|
|
153
|
+
created_by: 'string',
|
|
154
|
+
last_updated_by: 'string',
|
|
155
|
+
total_estimated_cost: 'number',
|
|
156
|
+
duration: 'number',
|
|
157
|
+
tags: 'string[]',
|
|
158
|
+
spans: 'array',
|
|
159
|
+
},
|
|
160
|
+
span: {
|
|
161
|
+
id: 'string',
|
|
162
|
+
trace_id: 'string',
|
|
163
|
+
name: 'string',
|
|
164
|
+
type: 'string',
|
|
165
|
+
start_time: 'timestamp',
|
|
166
|
+
end_time: 'timestamp',
|
|
167
|
+
input: 'object',
|
|
168
|
+
output: 'object',
|
|
169
|
+
metadata: 'object',
|
|
170
|
+
usage: 'object',
|
|
171
|
+
duration: 'number',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
metrics: {
|
|
176
|
+
available: true,
|
|
177
|
+
features: [
|
|
178
|
+
'Track token usage over time',
|
|
179
|
+
'Monitor costs and usage patterns',
|
|
180
|
+
'Filter metrics by project',
|
|
181
|
+
'Date range filtering',
|
|
182
|
+
'Aggregate metrics by day/week/month',
|
|
183
|
+
'LLM evaluation metrics (heuristic and LLM-as-judge)',
|
|
184
|
+
'Evaluation metrics for hallucination detection',
|
|
185
|
+
'Evaluation metrics for RAG evaluation',
|
|
186
|
+
'Evaluation metrics for moderation',
|
|
187
|
+
'Production monitoring dashboards',
|
|
188
|
+
'Rules and alerts for metrics',
|
|
189
|
+
],
|
|
190
|
+
limitations: [
|
|
191
|
+
'Limited custom metric definitions',
|
|
192
|
+
'Limited visualization options through API',
|
|
193
|
+
'No real-time metrics streaming',
|
|
194
|
+
'Limited alerting capabilities',
|
|
195
|
+
'Limited export functionality',
|
|
196
|
+
],
|
|
197
|
+
examples: [
|
|
198
|
+
'Tracking monthly token usage',
|
|
199
|
+
'Monitoring cost trends over time',
|
|
200
|
+
'Evaluating LLM outputs for hallucinations',
|
|
201
|
+
'Measuring RAG performance with context precision/recall',
|
|
202
|
+
'Setting up production monitoring dashboards',
|
|
203
|
+
'Creating rules for metric thresholds',
|
|
204
|
+
],
|
|
205
|
+
availableMetrics: [
|
|
206
|
+
'total_tokens',
|
|
207
|
+
'prompt_tokens',
|
|
208
|
+
'completion_tokens',
|
|
209
|
+
'cost',
|
|
210
|
+
'latency',
|
|
211
|
+
'requests_count',
|
|
212
|
+
'error_rate',
|
|
213
|
+
'hallucination_score',
|
|
214
|
+
'answer_relevance',
|
|
215
|
+
'context_precision',
|
|
216
|
+
'context_recall',
|
|
217
|
+
'moderation_score',
|
|
218
|
+
],
|
|
219
|
+
customMetricsSupport: true,
|
|
220
|
+
visualizationSupport: true,
|
|
221
|
+
schema: {
|
|
222
|
+
metric: {
|
|
223
|
+
name: 'string',
|
|
224
|
+
description: 'string',
|
|
225
|
+
value: 'number',
|
|
226
|
+
unit: 'string',
|
|
227
|
+
timestamp: 'timestamp',
|
|
228
|
+
project_id: 'string',
|
|
229
|
+
},
|
|
230
|
+
evaluation: {
|
|
231
|
+
id: 'string',
|
|
232
|
+
name: 'string',
|
|
233
|
+
metric: 'string',
|
|
234
|
+
score: 'number',
|
|
235
|
+
details: 'object',
|
|
236
|
+
timestamp: 'timestamp',
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
general: {
|
|
241
|
+
apiVersion: 'v1',
|
|
242
|
+
authentication: 'API Key via authorization header',
|
|
243
|
+
rateLimit: 'Configurable, high volume support (40+ million traces per day)',
|
|
244
|
+
supportedFormats: ['JSON'],
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* Get capabilities information based on configuration
|
|
249
|
+
* @param config The current configuration
|
|
250
|
+
* @returns Filtered capabilities based on what's enabled
|
|
251
|
+
*/
|
|
252
|
+
export function getEnabledCapabilities(config) {
|
|
253
|
+
return {
|
|
254
|
+
prompts: config.mcpEnablePromptTools
|
|
255
|
+
? opikCapabilities.prompts
|
|
256
|
+
: { available: false, features: [], limitations: [] },
|
|
257
|
+
projects: config.mcpEnableProjectTools
|
|
258
|
+
? opikCapabilities.projects
|
|
259
|
+
: { available: false, features: [], limitations: [] },
|
|
260
|
+
traces: config.mcpEnableTraceTools
|
|
261
|
+
? opikCapabilities.traces
|
|
262
|
+
: { available: false, features: [], limitations: [] },
|
|
263
|
+
metrics: config.mcpEnableMetricTools
|
|
264
|
+
? opikCapabilities.metrics
|
|
265
|
+
: { available: false, features: [], limitations: [] },
|
|
266
|
+
general: opikCapabilities.general,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get a description of what Opik Comet can and cannot do
|
|
271
|
+
* @param config The current configuration
|
|
272
|
+
* @returns A string description of capabilities
|
|
273
|
+
*/
|
|
274
|
+
export function getCapabilitiesDescription(config) {
|
|
275
|
+
const capabilities = getEnabledCapabilities(config);
|
|
276
|
+
let description = 'Opik Comet Capabilities:\n\n';
|
|
277
|
+
// General capabilities
|
|
278
|
+
description += 'General:\n';
|
|
279
|
+
description += `- API Version: ${capabilities.general?.apiVersion || 'v1'}\n`;
|
|
280
|
+
description += `- Authentication: ${capabilities.general?.authentication || 'API Key'}\n`;
|
|
281
|
+
description += `- Rate Limit: ${capabilities.general?.rateLimit || 'Default'}\n\n`;
|
|
282
|
+
// Add each capability section
|
|
283
|
+
for (const [key, capability] of Object.entries(capabilities)) {
|
|
284
|
+
if (key === 'general')
|
|
285
|
+
continue;
|
|
286
|
+
const cap = capability;
|
|
287
|
+
if (!cap.available) {
|
|
288
|
+
description += `${key.charAt(0).toUpperCase() + key.slice(1)}: Not available\n\n`;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
description += `${key.charAt(0).toUpperCase() + key.slice(1)}:\n`;
|
|
292
|
+
description += 'Features:\n';
|
|
293
|
+
cap.features.forEach(feature => {
|
|
294
|
+
description += `- ${feature}\n`;
|
|
295
|
+
});
|
|
296
|
+
description += '\nLimitations:\n';
|
|
297
|
+
cap.limitations.forEach(limitation => {
|
|
298
|
+
description += `- ${limitation}\n`;
|
|
299
|
+
});
|
|
300
|
+
description += '\n';
|
|
301
|
+
}
|
|
302
|
+
return description;
|
|
303
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment loading utility
|
|
3
|
+
* Loads variables from .env file
|
|
4
|
+
*/
|
|
5
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* File-based logger
|
|
10
|
+
* Only writes if debug mode is enabled
|
|
11
|
+
*/
|
|
12
|
+
function writeToLogFile(message) {
|
|
13
|
+
try {
|
|
14
|
+
// This uses sync functions which is acceptable during initialization
|
|
15
|
+
// The log file will only be written to if DEBUG_MODE=true
|
|
16
|
+
if (process.env.DEBUG_MODE === 'true') {
|
|
17
|
+
const logFile = '/tmp/opik-mcp.log';
|
|
18
|
+
if (!fs.existsSync(logFile)) {
|
|
19
|
+
fs.writeFileSync(logFile, `Opik MCP Server Started: ${new Date().toISOString()}\n`);
|
|
20
|
+
}
|
|
21
|
+
fs.appendFileSync(logFile, `[${new Date().toISOString()}] [env] ${message}\n`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
// Silently fail if we can't write to the log file
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Attempts to load environment variables from .env file
|
|
30
|
+
* Falls back to .env.example if .env doesn't exist
|
|
31
|
+
*/
|
|
32
|
+
export function loadEnv() {
|
|
33
|
+
const envPath = path.resolve(process.cwd(), '.env');
|
|
34
|
+
const examplePath = path.resolve(process.cwd(), '.env.example');
|
|
35
|
+
// Check if .env exists
|
|
36
|
+
if (fs.existsSync(envPath)) {
|
|
37
|
+
// Log this to file instead of console
|
|
38
|
+
writeToLogFile('Loading environment from .env file');
|
|
39
|
+
dotenvConfig({ path: envPath });
|
|
40
|
+
}
|
|
41
|
+
else if (fs.existsSync(examplePath)) {
|
|
42
|
+
// Fall back to .env.example if .env doesn't exist
|
|
43
|
+
writeToLogFile('Warning: .env file not found, using .env.example as fallback');
|
|
44
|
+
writeToLogFile('Please create a .env file with your actual configuration');
|
|
45
|
+
dotenvConfig({ path: examplePath });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
writeToLogFile('Warning: No .env or .env.example file found');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Load environment variables when this module is imported
|
|
52
|
+
loadEnv();
|