#
tokens: 5072/50000 9/9 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
# AgentOps API Key - Required for authentication
AGENTOPS_API_KEY="your-agentops-api-key-here"

# Optional: Override the default API host (defaults to https://api.agentops.ai)
# HOST="https://api.agentops.ai"

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# TypeScript
*.tsbuildinfo

# Coverage directory used by tools like istanbul
coverage/
*.lcov

# Dependency directories
node_modules/
jspm_packages/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# Environments
.env
.envrc
.env.local
.env.development.local
.env.test.local
.env.production.local

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Logs
logs
*.log

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# Temporary files
*.tmp
*.temp

# Build artifacts
build/
dist/
tmp/

# Package files
*.tar.gz
*.zip

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# AgentOps MCP Server

[![smithery badge](https://smithery.ai/badge/@AgentOps-AI/agentops-mcp)](https://smithery.ai/server/@AgentOps-AI/agentops-mcp)

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.

## Usage

### MCP Client Configuration

Add the following to your MCP configuration file:

```json
{
    "mcpServers": {
        "agentops-mcp": {
            "command": "npx",
            "args": ["agentops-mcp"],
            "env": {
              "AGENTOPS_API_KEY": ""
            }
        }
    }
}
```

## Installation

### Installing via Cursor Deeplink

[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=agentops&config=eyJjb21tYW5kIjoibnB4IGFnZW50b3BzLW1jcCIsImVudiI6eyJBR0VOVE9QU19BUElfS0VZIjoiIn19)

### Installing via Smithery

To install agentops-mcp for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@AgentOps-AI/agentops-mcp):

```bash
npx -y @smithery/cli install @AgentOps-AI/agentops-mcp --client claude
```

### Local Development

To build the MCP server locally:

```bash
# Clone and setup
git clone https://github.com/AgentOps-AI/agentops-mcp.git
cd mcp
npm install

# Build the project
npm run build

# Run the server
npm pack
```

## Available Tools

### `auth`
Authorize using an AgentOps project API key and return JWT token.

**Parameters:**
- `api_key` (string): Your AgentOps project API key

### `get_trace`
Retrieve trace information by ID.

**Parameters:**
- `trace_id` (string): The trace ID to retrieve

### `get_span`
Get span information by ID.

**Parameters:**
- `span_id` (string): The span ID to retrieve

### `get_complete_trace`
Get comprehensive trace information including all spans and their metrics.

**Parameters:**
- `trace_id` (string): The trace ID

## Requirements

- Node.js >= 18.0.0
- AgentOps API key (passed as parameter to tools)

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config
# Dockerfile for AgentOps MCP Server

FROM node:lts-alpine AS builder
WORKDIR /app

# Install dependencies and build
COPY package.json package-lock.json tsconfig.json ./
COPY src ./src
RUN npm install --ignore-scripts && npm run build

# Production image
FROM node:lts-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY README.md ./README.md

ENV NODE_ENV=production
ENTRYPOINT ["node", "dist/server.js"]

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/build/project-config

startCommand:
  type: stdio
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({
      command: 'node',
      args: ['dist/index.js'],
      env: config.apiKey ? { AGENTOPS_API_KEY: config.apiKey } : {}
    })
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required: []
    properties:
      apiKey:
        type: string
        description: AgentOps project API key (optional)
  exampleConfig: {}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "agentops-mcp",
  "version": "0.3.5",
  "description": "MCP index access to the AgentOps Public API.",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "bin": {
    "agentops-mcp": "dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts",
    "prepublishOnly": "npm run build"
  },
  "keywords": [
    "mcp",
    "agentops",
    "observability",
    "ai",
    "monitoring"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.4.0",
    "axios": "^1.6.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "tsx": "^4.0.0",
    "typescript": "^5.0.0"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "files": [
    "dist/**/*",
    "README.md"
  ]
}

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "removeComments": false,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noUncheckedIndexedAccess": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/.github/workflows/issue-solver.yml:
--------------------------------------------------------------------------------

```yaml

name: Entelligence-AI
permissions:
  contents: read
  issues: write
on:
  issues:
    types: [opened, edited]
jobs:
  handle_issues:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Extract Repository and Username
        id: extract-repo-info
        run: |
          repo_name=$(basename $GITHUB_REPOSITORY)
          username=$(dirname $GITHUB_REPOSITORY | cut -d'/' -f1)
          echo "REPO_NAME=$repo_name" >> $GITHUB_ENV
          echo "USERNAME=$username" >> $GITHUB_ENV

      - name: Sanitize Issue Body
        id: sanitize-body
        run: |
          sanitized_body=$(echo "${{ github.event.issue.body }}" | tr -d '\r' | tr '\n' ' ')
          echo "SANITIZED_BODY=${sanitized_body}" >> $GITHUB_ENV

      - name: Debug Sanitized Body
        run: |
          echo "Sanitized Body: ${{ env.SANITIZED_BODY }}"

      - name: Call API
        id: call-api
        env:
          API_URL: ${{ secrets.ENTELLIGENCE_AI_ISSUE_API }}
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_BODY: ${{ env.SANITIZED_BODY }}
          REPO_NAME: ${{ env.REPO_NAME }}
          USERNAME: ${{ env.USERNAME }}
        run: |
          set +e
          response=$(curl -s -X POST ${{env.API_URL}} \
          -H "Content-Type: application/json" \
          -d "{\"vectorDBUrl\": \"${{env.USERNAME}}&${{env.REPO_NAME}}\", \"title\": \"${{env.ISSUE_TITLE}}\", \"summary\": \"${{env.ISSUE_BODY}}\",  \"repoName\": \"${{env.USERNAME}}/${{env.REPO_NAME}}\"}")
          body=$(echo "$response" | sed '$d')
          echo "$response"
          echo "API_RESPONSE<<EOF" >> $GITHUB_ENV
          echo $(printf "%s" "$body" | base64) >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV
          set -e

      - name: Post Comment on Issue
        uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const issueNumber = context.issue.number;
            const apiResponse = Buffer.from(process.env.API_RESPONSE, 'base64').toString('utf-8');

            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: issueNumber,
              body: apiResponse
            });

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from "@modelcontextprotocol/sdk/types.js";
import axios, { AxiosResponse } from "axios";

const HOST = "https://api.agentops.ai";

interface AuthHeaders {
  [key: string]: string;
  Authorization: string;
}

interface ErrorResponse {
  error: string;
}

// Server state to hold JWT token
class ServerState {
  private jwtToken: string | null = null;

  setJwtToken(token: string): void {
    this.jwtToken = token;
  }

  getAuthHeaders(): AuthHeaders | null {
    if (!this.jwtToken) {
      return null;
    }
    return {
      Authorization: `Bearer ${this.jwtToken}`,
      "User-Agent": "agentops-mcp/0.3.5",
    };
  }

  isAuthenticated(): boolean {
    return this.jwtToken !== null;
  }

  clearAuth(): void {
    this.jwtToken = null;
  }
}

const serverState = new ServerState();

/**
 * Initialize authentication on server startup if API key is available
 */
async function initializeAuth(): Promise<void> {
  const apiKey = process.env["AGENTOPS_API_KEY"];
  if (apiKey) {
    try {
      const authResult = await authWithApiKey(apiKey);
      if (typeof authResult === "string") {
        serverState.setJwtToken(authResult);
        console.error(
          "Auto-authentication successful using environment variable",
        );
      } else {
        console.error(`Auto-authentication failed: ${authResult.error}`);
      }
    } catch (error) {
      console.error(
        `Auto-authentication error: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }
}

/**
 * Authorize using an AgentOps project API key and return JWT token
 */
async function authWithApiKey(apiKey: string): Promise<string | ErrorResponse> {
  const data = { api_key: apiKey };

  try {
    const response: AxiosResponse = await axios.post(
      `${HOST}/public/v1/auth/access_token`,
      data,
    );
    const bearer = response.data?.bearer;
    if (!bearer) {
      throw new Error("No bearer token received from auth endpoint");
    }
    return bearer;
  } catch (error) {
    return { error: error instanceof Error ? error.message : String(error) };
  }
}

/**
 * Clean response by removing empty values
 */
function clean(response: any): any {
  if (typeof response === "object" && response !== null) {
    if (Array.isArray(response)) {
      return response
        .map((item) => clean(item))
        .filter(
          (value) =>
            value !== "" &&
            value !== null &&
            !(Array.isArray(value) && value.length === 0) &&
            !(typeof value === "object" && Object.keys(value).length === 0),
        );
    } else {
      const cleaned: any = {};
      for (const [key, value] of Object.entries(response)) {
        const cleanedValue = clean(value);
        if (
          cleanedValue !== "" &&
          cleanedValue !== null &&
          !(Array.isArray(cleanedValue) && cleanedValue.length === 0) &&
          !(
            typeof cleanedValue === "object" &&
            Object.keys(cleanedValue).length === 0
          )
        ) {
          cleaned[key] = cleanedValue;
        }
      }
      return cleaned;
    }
  }
  return response;
}

/**
 * Make authenticated request to AgentOps API using stored JWT token
 */
async function makeAuthenticatedRequest(endpoint: string): Promise<any> {
  const authHeaders = serverState.getAuthHeaders();
  if (!authHeaders) {
    throw new Error(
      "Not authenticated. Please use the 'auth' tool first with your AgentOps API key.",
    );
  }

  try {
    const response = await axios.get(`${HOST}${endpoint}`, {
      headers: authHeaders,
    });
    return clean(response.data);
  } catch (error) {
    throw new Error(error instanceof Error ? error.message : String(error));
  }
}

const server = new Server({
  name: "agentops-mcp",
  version: "0.3.0",
});

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "auth",
        description:
          "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.",
        inputSchema: {
          type: "object",
          properties: {
            api_key: {
              type: "string",
              description:
                "AgentOps project API key (optional if AGENTOPS_API_KEY environment variable is set)",
            },
          },
          required: [],
        },
      },
      {
        name: "get_trace",
        description: "Get trace information and metrics by trace_id.",
        inputSchema: {
          type: "object",
          properties: {
            trace_id: {
              type: "string",
              description: "Trace ID",
            },
          },
          required: ["trace_id"],
        },
      },
      {
        name: "get_span",
        description: "Get span information and metrics by span_id.",
        inputSchema: {
          type: "object",
          properties: {
            span_id: {
              type: "string",
              description: "Span ID",
            },
          },
          required: ["span_id"],
        },
      },
      {
        name: "get_complete_trace",
        description:
          "Reserved for explicit requests for COMPLETE or ALL data. Get complete trace information and metrics by trace_id.",
        inputSchema: {
          type: "object",
          properties: {
            trace_id: {
              type: "string",
              description: "Trace ID",
            },
          },
          required: ["trace_id"],
        },
      },
    ],
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    switch (name) {
      case "auth": {
        const { api_key } = args as { api_key?: string };

        // Check if already authenticated
        if (serverState.isAuthenticated() && !api_key) {
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(
                  {
                    success: true,
                    message: "Already authenticated",
                    source:
                      "Previously authenticated (likely from environment variable on startup)",
                  },
                  null,
                  2,
                ),
              },
            ],
          };
        }

        // Try to get API key from environment first, then from parameter
        const actualApiKey = api_key || process.env["AGENTOPS_API_KEY"];
        if (!actualApiKey) {
          throw new Error(
            "No project API key available. Please provide a project API key.",
          );
        }

        const authResult = await authWithApiKey(actualApiKey);
        if (typeof authResult === "object" && "error" in authResult) {
          throw new Error(`Authentication failed: ${authResult.error}`);
        }

        // Store the JWT token in server state
        serverState.setJwtToken(authResult);

        const result = await makeAuthenticatedRequest(`/public/v1/project`);
        const name = result.name;
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  success: true,
                  message: "Authentication successful",
                  project: name,
                },
                null,
                2,
              ),
            },
          ],
        };
      }

      case "get_trace": {
        const { trace_id } = args as { trace_id: string };
        const [traceInfo, traceMetrics] = await Promise.all([
          makeAuthenticatedRequest(`/public/v1/traces/${trace_id}`),
          makeAuthenticatedRequest(`/public/v1/traces/${trace_id}/metrics`),
        ]);
        const result = { ...traceInfo, metrics: traceMetrics };
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      }

      case "get_span": {
        const { span_id } = args as { span_id: string };
        const [spanInfo, spanMetrics] = await Promise.all([
          makeAuthenticatedRequest(`/public/v1/spans/${span_id}`),
          makeAuthenticatedRequest(`/public/v1/spans/${span_id}/metrics`),
        ]);
        const result = { ...spanInfo, metrics: spanMetrics };
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      }

      case "get_complete_trace": {
        const { trace_id } = args as { trace_id: string };
        const [traceInfo, traceMetrics] = await Promise.all([
          makeAuthenticatedRequest(`/public/v1/traces/${trace_id}`),
          makeAuthenticatedRequest(`/public/v1/traces/${trace_id}/metrics`),
        ]);
        const parentTrace = { ...traceInfo, metrics: traceMetrics };

        if (parentTrace.spans && Array.isArray(parentTrace.spans)) {
          for (let i = 0; i < parentTrace.spans.length; i++) {
            if (parentTrace.spans[i].span_id) {
              const span_id = parentTrace.spans[i].span_id;
              const [childSpanInfo, childSpanMetrics] = await Promise.all([
                makeAuthenticatedRequest(`/public/v1/spans/${span_id}`),
                makeAuthenticatedRequest(`/public/v1/spans/${span_id}/metrics`),
              ]);
              parentTrace.spans[i] = {
                ...childSpanInfo,
                metrics: childSpanMetrics,
              };
            }
          }
        }

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(parentTrace, null, 2),
            },
          ],
        };
      }

      default:
        throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
    }
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({ error: errorMessage }, null, 2),
        },
      ],
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();

  // Initialize authentication before connecting
  await initializeAuth();

  await server.connect(transport);

  // Debug: Log environment variable and authentication status
  const hasApiKey = !!process.env["AGENTOPS_API_KEY"];
  const isAuthenticated = serverState.isAuthenticated();

  console.error("AgentOps MCP server running on stdio");
  console.error(
    `AGENTOPS_API_KEY environment variable: ${hasApiKey ? "SET" : "NOT SET"}`,
  );
  console.error(
    `Authentication status: ${isAuthenticated ? "AUTHENTICATED" : "NOT AUTHENTICATED"}`,
  );

  if (hasApiKey) {
    const keyPreview = process.env["AGENTOPS_API_KEY"]!.substring(0, 8) + "...";
    console.error(`API Key preview: ${keyPreview}`);
  }
}

// Handle process signals gracefully
process.on("SIGINT", () => {
  console.error("Received SIGINT, shutting down gracefully");
  process.exit(0);
});

process.on("SIGTERM", () => {
  console.error("Received SIGTERM, shutting down gracefully");
  process.exit(0);
});

// Handle uncaught exceptions
process.on("uncaughtException", (error) => {
  console.error("Uncaught exception:", error);
  process.exit(1);
});

process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled rejection at:", promise, "reason:", reason);
  process.exit(1);
});

if (require.main === module) {
  main().catch((error) => {
    console.error("Server error:", error);
    console.error("Stack trace:", error.stack);
    process.exit(1);
  });
}

```