#
tokens: 6962/50000 9/9 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | [![smithery badge](https://smithery.ai/badge/@AgentOps-AI/agentops-mcp)](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 | [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](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 | 
```