> ## Documentation Index
> Fetch the complete documentation index at: https://self-5d39fc87.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# StaticAnalyzer class reference

> Full reference for the StaticAnalyzer class: constructor config, the analyze() method signature, return shape, and code examples for common usage patterns.

`StaticAnalyzer` is the main class exported by `mcp-watchtower`. It runs five deterministic checks against a set of MCP tool definitions — duplicate names, naming convention inconsistencies, parameter conflicts, shadow patterns, and tool count — and returns a structured report. All checks run offline with no LLM calls.

## Constructor

```typescript theme={null}
new StaticAnalyzer(config?: StaticAnalyzerConfig)
```

Pass an optional configuration object to control how the analyzer behaves. All options have defaults, so you can construct the analyzer with no arguments.

```typescript theme={null}
import { StaticAnalyzer } from 'mcp-watchtower'

// Default configuration
const defaultAnalyzer = new StaticAnalyzer()

// Custom configuration
const customAnalyzer = new StaticAnalyzer({ platform: true, maxTools: 15 })
```

### Config options

<ParamField path="config.platform" type="boolean" default="false">
  When `true`, elevates `DUPLICATE_TOOL_NAME` findings from `warning` to `critical` severity. Use this when your application loads multiple MCP servers into the same context simultaneously, where name collisions cause genuine routing failures.
</ParamField>

<ParamField path="config.maxTools" type="number" default="20">
  The maximum number of tools a server may declare before a `TOOL_COUNT_WARNING` finding is emitted. Research shows LLM routing accuracy degrades above roughly 20 tools per server. Set this lower for more conservative thresholds.
</ParamField>

## analyze()

```typescript theme={null}
analyze(serverName: string, tools: ToolSchema[]): StaticReport
```

Runs all five static checks and returns a `StaticReport`. The method is synchronous — there are no network calls or async operations.

### Parameters

<ParamField path="serverName" type="string" required>
  A human-readable label for the server being analyzed. This value is included verbatim in the returned `StaticReport` as the `server` field and in finding messages. Use a stable identifier such as a package name or deployment name.
</ParamField>

<ParamField path="tools" type="ToolSchema[]" required>
  An array of tool definitions from the MCP server manifest. Each entry must include at least a `name` and `description`. See the [`ToolSchema` type reference](/api/types#toolschema) for the full shape.
</ParamField>

### Return value

`analyze()` returns a `StaticReport` object.

<ResponseField name="server" type="string" required>
  The `serverName` value you passed to `analyze()`.
</ResponseField>

<ResponseField name="toolCount" type="number" required>
  The number of tools in the array you passed to `analyze()`.
</ResponseField>

<ResponseField name="findings" type="Finding[]" required>
  All findings produced by the five checks. An empty array means no issues were detected. See the [`Finding` type reference](/api/types#finding) for the full shape and all possible `code` values.
</ResponseField>

<ResponseField name="passedAt" type="string" required>
  ISO 8601 timestamp of when the analysis completed, e.g. `"2024-11-15T09:32:00.123Z"`.
</ResponseField>

## Code examples

<CodeGroup>
  ```typescript Basic usage theme={null}
  import { StaticAnalyzer } from 'mcp-watchtower'
  import type { ToolSchema } from 'mcp-watchtower'

  const tools: ToolSchema[] = [
    {
      name: 'search_documents',
      description: 'Search through stored documents.',
      inputSchema: {
        type: 'object',
        properties: {
          query: { type: 'string', description: 'Search query' },
        },
        required: ['query'],
      },
    },
  ]

  const analyzer = new StaticAnalyzer({ maxTools: 20 })
  const report = analyzer.analyze('my-server', tools)

  console.log(`Analyzed ${report.toolCount} tool(s) for "${report.server}"`)
  console.log(`Findings: ${report.findings.length}`)
  ```

  ```typescript Checking for criticals theme={null}
  import { StaticAnalyzer } from 'mcp-watchtower'
  import type { ToolSchema } from 'mcp-watchtower'

  async function lintTools(serverName: string, tools: ToolSchema[]) {
    const analyzer = new StaticAnalyzer({ platform: false, maxTools: 20 })
    const report = analyzer.analyze(serverName, tools)

    const criticals = report.findings.filter(f => f.severity === 'critical')
    const warnings = report.findings.filter(f => f.severity === 'warning')

    if (criticals.length > 0) {
      console.error(`FAIL: ${criticals.length} critical finding(s)`)
      for (const f of criticals) {
        console.error(`  [${f.code}] ${f.message}`)
      }
      return false
    }

    if (warnings.length > 0) {
      console.warn(`WARN: ${warnings.length} warning(s)`)
      for (const f of warnings) {
        console.warn(`  [${f.code}] ${f.message}`)
      }
    }

    console.log('PASS: no critical findings')
    return true
  }
  ```

  ```typescript Accessing findings by severity theme={null}
  import { StaticAnalyzer } from 'mcp-watchtower'
  import type { ToolSchema, Finding } from 'mcp-watchtower'

  const analyzer = new StaticAnalyzer()
  const report = analyzer.analyze('my-server', tools)

  // Group findings by severity
  const bySeverity = report.findings.reduce<Record<string, Finding[]>>(
    (acc, finding) => {
      acc[finding.severity] ??= []
      acc[finding.severity].push(finding)
      return acc
    },
    {}
  )

  for (const [severity, findings] of Object.entries(bySeverity)) {
    console.log(`\n${severity.toUpperCase()} (${findings.length})`)
    for (const f of findings) {
      const tool = f.tool ? ` [${f.tool}]` : ''
      console.log(`  ${f.code}${tool}: ${f.message}`)
    }
  }
  ```
</CodeGroup>

## Platform mode

When you build an orchestrator or MCP registry that loads more than one server into a shared context, duplicate tool names across servers are a real routing hazard — not just a style concern. Pass `platform: true` to treat `DUPLICATE_TOOL_NAME` as `critical`.

```typescript theme={null}
const analyzer = new StaticAnalyzer({ platform: true })
const report = analyzer.analyze('server-a', toolsA)

// DUPLICATE_TOOL_NAME findings will now have severity: 'critical'
```

<Info>
  `platform: true` only affects the severity of `DUPLICATE_TOOL_NAME` findings. All other finding codes keep their default severities regardless of this setting.
</Info>
