# Directory Structure ``` ├── .env.example ├── .github │ └── workflows │ └── issue-solver.yml ├── .gitignore ├── Dockerfile ├── package-lock.json ├── package.json ├── README.md ├── smithery.yaml ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` 1 | # AgentOps API Key - Required for authentication 2 | AGENTOPS_API_KEY="your-agentops-api-key-here" 3 | 4 | # Optional: Override the default API host (defaults to https://api.agentops.ai) 5 | # HOST="https://api.agentops.ai" 6 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Node.js 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | lerna-debug.log* 7 | 8 | # TypeScript 9 | *.tsbuildinfo 10 | 11 | # Coverage directory used by tools like istanbul 12 | coverage/ 13 | *.lcov 14 | 15 | # Dependency directories 16 | node_modules/ 17 | jspm_packages/ 18 | 19 | # Optional npm cache directory 20 | .npm 21 | 22 | # Optional eslint cache 23 | .eslintcache 24 | 25 | # Optional REPL history 26 | .node_repl_history 27 | 28 | # Output of 'npm pack' 29 | *.tgz 30 | 31 | # Yarn Integrity file 32 | .yarn-integrity 33 | 34 | # Environments 35 | .env 36 | .envrc 37 | .env.local 38 | .env.development.local 39 | .env.test.local 40 | .env.production.local 41 | 42 | # Runtime data 43 | pids 44 | *.pid 45 | *.seed 46 | *.pid.lock 47 | 48 | # Logs 49 | logs 50 | *.log 51 | 52 | # OS generated files 53 | .DS_Store 54 | .DS_Store? 55 | ._* 56 | .Spotlight-V100 57 | .Trashes 58 | ehthumbs.db 59 | Thumbs.db 60 | 61 | # IDE files 62 | .vscode/ 63 | .idea/ 64 | *.swp 65 | *.swo 66 | *~ 67 | 68 | # Temporary files 69 | *.tmp 70 | *.temp 71 | 72 | # Build artifacts 73 | build/ 74 | dist/ 75 | tmp/ 76 | 77 | # Package files 78 | *.tar.gz 79 | *.zip 80 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # AgentOps MCP Server 2 | 3 | [](https://smithery.ai/server/@AgentOps-AI/agentops-mcp) 4 | 5 | The AgentOps MCP server provides access to observability and tracing data for debugging complex AI agent runs. This adds crucial context about where the AI agent succeeds or fails. 6 | 7 | ## Usage 8 | 9 | ### MCP Client Configuration 10 | 11 | Add the following to your MCP configuration file: 12 | 13 | ```json 14 | { 15 | "mcpServers": { 16 | "agentops-mcp": { 17 | "command": "npx", 18 | "args": ["agentops-mcp"], 19 | "env": { 20 | "AGENTOPS_API_KEY": "" 21 | } 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | ## Installation 28 | 29 | ### Installing via Cursor Deeplink 30 | 31 | [](https://cursor.com/install-mcp?name=agentops&config=eyJjb21tYW5kIjoibnB4IGFnZW50b3BzLW1jcCIsImVudiI6eyJBR0VOVE9QU19BUElfS0VZIjoiIn19) 32 | 33 | ### Installing via Smithery 34 | 35 | To install agentops-mcp for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@AgentOps-AI/agentops-mcp): 36 | 37 | ```bash 38 | npx -y @smithery/cli install @AgentOps-AI/agentops-mcp --client claude 39 | ``` 40 | 41 | ### Local Development 42 | 43 | To build the MCP server locally: 44 | 45 | ```bash 46 | # Clone and setup 47 | git clone https://github.com/AgentOps-AI/agentops-mcp.git 48 | cd mcp 49 | npm install 50 | 51 | # Build the project 52 | npm run build 53 | 54 | # Run the server 55 | npm pack 56 | ``` 57 | 58 | ## Available Tools 59 | 60 | ### `auth` 61 | Authorize using an AgentOps project API key and return JWT token. 62 | 63 | **Parameters:** 64 | - `api_key` (string): Your AgentOps project API key 65 | 66 | ### `get_trace` 67 | Retrieve trace information by ID. 68 | 69 | **Parameters:** 70 | - `trace_id` (string): The trace ID to retrieve 71 | 72 | ### `get_span` 73 | Get span information by ID. 74 | 75 | **Parameters:** 76 | - `span_id` (string): The span ID to retrieve 77 | 78 | ### `get_complete_trace` 79 | Get comprehensive trace information including all spans and their metrics. 80 | 81 | **Parameters:** 82 | - `trace_id` (string): The trace ID 83 | 84 | ## Requirements 85 | 86 | - Node.js >= 18.0.0 87 | - AgentOps API key (passed as parameter to tools) 88 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config 2 | # Dockerfile for AgentOps MCP Server 3 | 4 | FROM node:lts-alpine AS builder 5 | WORKDIR /app 6 | 7 | # Install dependencies and build 8 | COPY package.json package-lock.json tsconfig.json ./ 9 | COPY src ./src 10 | RUN npm install --ignore-scripts && npm run build 11 | 12 | # Production image 13 | FROM node:lts-alpine 14 | WORKDIR /app 15 | COPY --from=builder /app/dist ./dist 16 | COPY --from=builder /app/node_modules ./node_modules 17 | COPY README.md ./README.md 18 | 19 | ENV NODE_ENV=production 20 | ENTRYPOINT ["node", "dist/server.js"] 21 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery configuration file: https://smithery.ai/docs/build/project-config 2 | 3 | startCommand: 4 | type: stdio 5 | commandFunction: 6 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio. 7 | |- 8 | (config) => ({ 9 | command: 'node', 10 | args: ['dist/index.js'], 11 | env: config.apiKey ? { AGENTOPS_API_KEY: config.apiKey } : {} 12 | }) 13 | configSchema: 14 | # JSON Schema defining the configuration options for the MCP. 15 | type: object 16 | required: [] 17 | properties: 18 | apiKey: 19 | type: string 20 | description: AgentOps project API key (optional) 21 | exampleConfig: {} 22 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "agentops-mcp", 3 | "version": "0.3.5", 4 | "description": "MCP index access to the AgentOps Public API.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "bin": { 8 | "agentops-mcp": "dist/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "start": "node dist/index.js", 13 | "dev": "tsx src/index.ts", 14 | "prepublishOnly": "npm run build" 15 | }, 16 | "keywords": [ 17 | "mcp", 18 | "agentops", 19 | "observability", 20 | "ai", 21 | "monitoring" 22 | ], 23 | "author": "", 24 | "license": "MIT", 25 | "dependencies": { 26 | "@modelcontextprotocol/sdk": "^0.4.0", 27 | "axios": "^1.6.0" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^20.0.0", 31 | "tsx": "^4.0.0", 32 | "typescript": "^5.0.0" 33 | }, 34 | "engines": { 35 | "node": ">=18.0.0" 36 | }, 37 | "files": [ 38 | "dist/**/*", 39 | "README.md" 40 | ] 41 | } 42 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "CommonJS", 5 | "lib": ["ES2020"], 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "sourceMap": true, 15 | "removeComments": false, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "exactOptionalPropertyTypes": true, 22 | "noImplicitOverride": true, 23 | "noPropertyAccessFromIndexSignature": true, 24 | "noUncheckedIndexedAccess": true, 25 | "moduleResolution": "node", 26 | "resolveJsonModule": true, 27 | "allowSyntheticDefaultImports": true 28 | }, 29 | "include": ["src/**/*"], 30 | "exclude": ["node_modules", "dist"] 31 | } 32 | ``` -------------------------------------------------------------------------------- /.github/workflows/issue-solver.yml: -------------------------------------------------------------------------------- ```yaml 1 | 2 | name: Entelligence-AI 3 | permissions: 4 | contents: read 5 | issues: write 6 | on: 7 | issues: 8 | types: [opened, edited] 9 | jobs: 10 | handle_issues: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup Node.js 20 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: '20' 20 | 21 | - name: Extract Repository and Username 22 | id: extract-repo-info 23 | run: | 24 | repo_name=$(basename $GITHUB_REPOSITORY) 25 | username=$(dirname $GITHUB_REPOSITORY | cut -d'/' -f1) 26 | echo "REPO_NAME=$repo_name" >> $GITHUB_ENV 27 | echo "USERNAME=$username" >> $GITHUB_ENV 28 | 29 | - name: Sanitize Issue Body 30 | id: sanitize-body 31 | run: | 32 | sanitized_body=$(echo "${{ github.event.issue.body }}" | tr -d '\r' | tr '\n' ' ') 33 | echo "SANITIZED_BODY=${sanitized_body}" >> $GITHUB_ENV 34 | 35 | - name: Debug Sanitized Body 36 | run: | 37 | echo "Sanitized Body: ${{ env.SANITIZED_BODY }}" 38 | 39 | - name: Call API 40 | id: call-api 41 | env: 42 | API_URL: ${{ secrets.ENTELLIGENCE_AI_ISSUE_API }} 43 | ISSUE_TITLE: ${{ github.event.issue.title }} 44 | ISSUE_BODY: ${{ env.SANITIZED_BODY }} 45 | REPO_NAME: ${{ env.REPO_NAME }} 46 | USERNAME: ${{ env.USERNAME }} 47 | run: | 48 | set +e 49 | response=$(curl -s -X POST ${{env.API_URL}} \ 50 | -H "Content-Type: application/json" \ 51 | -d "{\"vectorDBUrl\": \"${{env.USERNAME}}&${{env.REPO_NAME}}\", \"title\": \"${{env.ISSUE_TITLE}}\", \"summary\": \"${{env.ISSUE_BODY}}\", \"repoName\": \"${{env.USERNAME}}/${{env.REPO_NAME}}\"}") 52 | body=$(echo "$response" | sed '$d') 53 | echo "$response" 54 | echo "API_RESPONSE<<EOF" >> $GITHUB_ENV 55 | echo $(printf "%s" "$body" | base64) >> $GITHUB_ENV 56 | echo "EOF" >> $GITHUB_ENV 57 | set -e 58 | 59 | - name: Post Comment on Issue 60 | uses: actions/github-script@v6 61 | with: 62 | github-token: ${{ secrets.GITHUB_TOKEN }} 63 | script: | 64 | const issueNumber = context.issue.number; 65 | const apiResponse = Buffer.from(process.env.API_RESPONSE, 'base64').toString('utf-8'); 66 | 67 | github.rest.issues.createComment({ 68 | owner: context.repo.owner, 69 | repo: context.repo.repo, 70 | issue_number: issueNumber, 71 | body: apiResponse 72 | }); 73 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 | import { 6 | CallToolRequestSchema, 7 | ErrorCode, 8 | ListToolsRequestSchema, 9 | McpError, 10 | } from "@modelcontextprotocol/sdk/types.js"; 11 | import axios, { AxiosResponse } from "axios"; 12 | 13 | const HOST = "https://api.agentops.ai"; 14 | 15 | interface AuthHeaders { 16 | [key: string]: string; 17 | Authorization: string; 18 | } 19 | 20 | interface ErrorResponse { 21 | error: string; 22 | } 23 | 24 | // Server state to hold JWT token 25 | class ServerState { 26 | private jwtToken: string | null = null; 27 | 28 | setJwtToken(token: string): void { 29 | this.jwtToken = token; 30 | } 31 | 32 | getAuthHeaders(): AuthHeaders | null { 33 | if (!this.jwtToken) { 34 | return null; 35 | } 36 | return { 37 | Authorization: `Bearer ${this.jwtToken}`, 38 | "User-Agent": "agentops-mcp/0.3.5", 39 | }; 40 | } 41 | 42 | isAuthenticated(): boolean { 43 | return this.jwtToken !== null; 44 | } 45 | 46 | clearAuth(): void { 47 | this.jwtToken = null; 48 | } 49 | } 50 | 51 | const serverState = new ServerState(); 52 | 53 | /** 54 | * Initialize authentication on server startup if API key is available 55 | */ 56 | async function initializeAuth(): Promise<void> { 57 | const apiKey = process.env["AGENTOPS_API_KEY"]; 58 | if (apiKey) { 59 | try { 60 | const authResult = await authWithApiKey(apiKey); 61 | if (typeof authResult === "string") { 62 | serverState.setJwtToken(authResult); 63 | console.error( 64 | "Auto-authentication successful using environment variable", 65 | ); 66 | } else { 67 | console.error(`Auto-authentication failed: ${authResult.error}`); 68 | } 69 | } catch (error) { 70 | console.error( 71 | `Auto-authentication error: ${error instanceof Error ? error.message : String(error)}`, 72 | ); 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * Authorize using an AgentOps project API key and return JWT token 79 | */ 80 | async function authWithApiKey(apiKey: string): Promise<string | ErrorResponse> { 81 | const data = { api_key: apiKey }; 82 | 83 | try { 84 | const response: AxiosResponse = await axios.post( 85 | `${HOST}/public/v1/auth/access_token`, 86 | data, 87 | ); 88 | const bearer = response.data?.bearer; 89 | if (!bearer) { 90 | throw new Error("No bearer token received from auth endpoint"); 91 | } 92 | return bearer; 93 | } catch (error) { 94 | return { error: error instanceof Error ? error.message : String(error) }; 95 | } 96 | } 97 | 98 | /** 99 | * Clean response by removing empty values 100 | */ 101 | function clean(response: any): any { 102 | if (typeof response === "object" && response !== null) { 103 | if (Array.isArray(response)) { 104 | return response 105 | .map((item) => clean(item)) 106 | .filter( 107 | (value) => 108 | value !== "" && 109 | value !== null && 110 | !(Array.isArray(value) && value.length === 0) && 111 | !(typeof value === "object" && Object.keys(value).length === 0), 112 | ); 113 | } else { 114 | const cleaned: any = {}; 115 | for (const [key, value] of Object.entries(response)) { 116 | const cleanedValue = clean(value); 117 | if ( 118 | cleanedValue !== "" && 119 | cleanedValue !== null && 120 | !(Array.isArray(cleanedValue) && cleanedValue.length === 0) && 121 | !( 122 | typeof cleanedValue === "object" && 123 | Object.keys(cleanedValue).length === 0 124 | ) 125 | ) { 126 | cleaned[key] = cleanedValue; 127 | } 128 | } 129 | return cleaned; 130 | } 131 | } 132 | return response; 133 | } 134 | 135 | /** 136 | * Make authenticated request to AgentOps API using stored JWT token 137 | */ 138 | async function makeAuthenticatedRequest(endpoint: string): Promise<any> { 139 | const authHeaders = serverState.getAuthHeaders(); 140 | if (!authHeaders) { 141 | throw new Error( 142 | "Not authenticated. Please use the 'auth' tool first with your AgentOps API key.", 143 | ); 144 | } 145 | 146 | try { 147 | const response = await axios.get(`${HOST}${endpoint}`, { 148 | headers: authHeaders, 149 | }); 150 | return clean(response.data); 151 | } catch (error) { 152 | throw new Error(error instanceof Error ? error.message : String(error)); 153 | } 154 | } 155 | 156 | const server = new Server({ 157 | name: "agentops-mcp", 158 | version: "0.3.0", 159 | }); 160 | 161 | // List available tools 162 | server.setRequestHandler(ListToolsRequestSchema, async () => { 163 | return { 164 | tools: [ 165 | { 166 | name: "auth", 167 | description: 168 | "Authorize using the AGENTOPS_API_KEY. If the API key is not provided and cannot be found in the directory, ask the user for the API key.", 169 | inputSchema: { 170 | type: "object", 171 | properties: { 172 | api_key: { 173 | type: "string", 174 | description: 175 | "AgentOps project API key (optional if AGENTOPS_API_KEY environment variable is set)", 176 | }, 177 | }, 178 | required: [], 179 | }, 180 | }, 181 | { 182 | name: "get_trace", 183 | description: "Get trace information and metrics by trace_id.", 184 | inputSchema: { 185 | type: "object", 186 | properties: { 187 | trace_id: { 188 | type: "string", 189 | description: "Trace ID", 190 | }, 191 | }, 192 | required: ["trace_id"], 193 | }, 194 | }, 195 | { 196 | name: "get_span", 197 | description: "Get span information and metrics by span_id.", 198 | inputSchema: { 199 | type: "object", 200 | properties: { 201 | span_id: { 202 | type: "string", 203 | description: "Span ID", 204 | }, 205 | }, 206 | required: ["span_id"], 207 | }, 208 | }, 209 | { 210 | name: "get_complete_trace", 211 | description: 212 | "Reserved for explicit requests for COMPLETE or ALL data. Get complete trace information and metrics by trace_id.", 213 | inputSchema: { 214 | type: "object", 215 | properties: { 216 | trace_id: { 217 | type: "string", 218 | description: "Trace ID", 219 | }, 220 | }, 221 | required: ["trace_id"], 222 | }, 223 | }, 224 | ], 225 | }; 226 | }); 227 | 228 | // Handle tool calls 229 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 230 | const { name, arguments: args } = request.params; 231 | 232 | try { 233 | switch (name) { 234 | case "auth": { 235 | const { api_key } = args as { api_key?: string }; 236 | 237 | // Check if already authenticated 238 | if (serverState.isAuthenticated() && !api_key) { 239 | return { 240 | content: [ 241 | { 242 | type: "text", 243 | text: JSON.stringify( 244 | { 245 | success: true, 246 | message: "Already authenticated", 247 | source: 248 | "Previously authenticated (likely from environment variable on startup)", 249 | }, 250 | null, 251 | 2, 252 | ), 253 | }, 254 | ], 255 | }; 256 | } 257 | 258 | // Try to get API key from environment first, then from parameter 259 | const actualApiKey = api_key || process.env["AGENTOPS_API_KEY"]; 260 | if (!actualApiKey) { 261 | throw new Error( 262 | "No project API key available. Please provide a project API key.", 263 | ); 264 | } 265 | 266 | const authResult = await authWithApiKey(actualApiKey); 267 | if (typeof authResult === "object" && "error" in authResult) { 268 | throw new Error(`Authentication failed: ${authResult.error}`); 269 | } 270 | 271 | // Store the JWT token in server state 272 | serverState.setJwtToken(authResult); 273 | 274 | const result = await makeAuthenticatedRequest(`/public/v1/project`); 275 | const name = result.name; 276 | return { 277 | content: [ 278 | { 279 | type: "text", 280 | text: JSON.stringify( 281 | { 282 | success: true, 283 | message: "Authentication successful", 284 | project: name, 285 | }, 286 | null, 287 | 2, 288 | ), 289 | }, 290 | ], 291 | }; 292 | } 293 | 294 | case "get_trace": { 295 | const { trace_id } = args as { trace_id: string }; 296 | const [traceInfo, traceMetrics] = await Promise.all([ 297 | makeAuthenticatedRequest(`/public/v1/traces/${trace_id}`), 298 | makeAuthenticatedRequest(`/public/v1/traces/${trace_id}/metrics`), 299 | ]); 300 | const result = { ...traceInfo, metrics: traceMetrics }; 301 | return { 302 | content: [ 303 | { 304 | type: "text", 305 | text: JSON.stringify(result, null, 2), 306 | }, 307 | ], 308 | }; 309 | } 310 | 311 | case "get_span": { 312 | const { span_id } = args as { span_id: string }; 313 | const [spanInfo, spanMetrics] = await Promise.all([ 314 | makeAuthenticatedRequest(`/public/v1/spans/${span_id}`), 315 | makeAuthenticatedRequest(`/public/v1/spans/${span_id}/metrics`), 316 | ]); 317 | const result = { ...spanInfo, metrics: spanMetrics }; 318 | return { 319 | content: [ 320 | { 321 | type: "text", 322 | text: JSON.stringify(result, null, 2), 323 | }, 324 | ], 325 | }; 326 | } 327 | 328 | case "get_complete_trace": { 329 | const { trace_id } = args as { trace_id: string }; 330 | const [traceInfo, traceMetrics] = await Promise.all([ 331 | makeAuthenticatedRequest(`/public/v1/traces/${trace_id}`), 332 | makeAuthenticatedRequest(`/public/v1/traces/${trace_id}/metrics`), 333 | ]); 334 | const parentTrace = { ...traceInfo, metrics: traceMetrics }; 335 | 336 | if (parentTrace.spans && Array.isArray(parentTrace.spans)) { 337 | for (let i = 0; i < parentTrace.spans.length; i++) { 338 | if (parentTrace.spans[i].span_id) { 339 | const span_id = parentTrace.spans[i].span_id; 340 | const [childSpanInfo, childSpanMetrics] = await Promise.all([ 341 | makeAuthenticatedRequest(`/public/v1/spans/${span_id}`), 342 | makeAuthenticatedRequest(`/public/v1/spans/${span_id}/metrics`), 343 | ]); 344 | parentTrace.spans[i] = { 345 | ...childSpanInfo, 346 | metrics: childSpanMetrics, 347 | }; 348 | } 349 | } 350 | } 351 | 352 | return { 353 | content: [ 354 | { 355 | type: "text", 356 | text: JSON.stringify(parentTrace, null, 2), 357 | }, 358 | ], 359 | }; 360 | } 361 | 362 | default: 363 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); 364 | } 365 | } catch (error) { 366 | const errorMessage = error instanceof Error ? error.message : String(error); 367 | return { 368 | content: [ 369 | { 370 | type: "text", 371 | text: JSON.stringify({ error: errorMessage }, null, 2), 372 | }, 373 | ], 374 | }; 375 | } 376 | }); 377 | 378 | async function main() { 379 | const transport = new StdioServerTransport(); 380 | 381 | // Initialize authentication before connecting 382 | await initializeAuth(); 383 | 384 | await server.connect(transport); 385 | 386 | // Debug: Log environment variable and authentication status 387 | const hasApiKey = !!process.env["AGENTOPS_API_KEY"]; 388 | const isAuthenticated = serverState.isAuthenticated(); 389 | 390 | console.error("AgentOps MCP server running on stdio"); 391 | console.error( 392 | `AGENTOPS_API_KEY environment variable: ${hasApiKey ? "SET" : "NOT SET"}`, 393 | ); 394 | console.error( 395 | `Authentication status: ${isAuthenticated ? "AUTHENTICATED" : "NOT AUTHENTICATED"}`, 396 | ); 397 | 398 | if (hasApiKey) { 399 | const keyPreview = process.env["AGENTOPS_API_KEY"]!.substring(0, 8) + "..."; 400 | console.error(`API Key preview: ${keyPreview}`); 401 | } 402 | } 403 | 404 | // Handle process signals gracefully 405 | process.on("SIGINT", () => { 406 | console.error("Received SIGINT, shutting down gracefully"); 407 | process.exit(0); 408 | }); 409 | 410 | process.on("SIGTERM", () => { 411 | console.error("Received SIGTERM, shutting down gracefully"); 412 | process.exit(0); 413 | }); 414 | 415 | // Handle uncaught exceptions 416 | process.on("uncaughtException", (error) => { 417 | console.error("Uncaught exception:", error); 418 | process.exit(1); 419 | }); 420 | 421 | process.on("unhandledRejection", (reason, promise) => { 422 | console.error("Unhandled rejection at:", promise, "reason:", reason); 423 | process.exit(1); 424 | }); 425 | 426 | if (require.main === module) { 427 | main().catch((error) => { 428 | console.error("Server error:", error); 429 | console.error("Stack trace:", error.stack); 430 | process.exit(1); 431 | }); 432 | } 433 | ```