Cut LLM Context Usage by Up to 90%: Filter Azure DevOps MCP Tools with a Proxy Server

By , Senior Full-Stack EngineerUpdated 15 min read

Claude Sonnet and many other LLMs ship with context windows in the 200k–1M token range, but that space fills up faster than you'd think when working with Model Context Protocol (MCP) servers. The official Azure DevOps MCP server is a perfect example of this problem: it exposes 70 tools covering builds, releases, repositories, wikis, test plans, and work items - consuming 55,125 tokens just to describe available functionality. The token count was measured against the Azure DevOps MCP server in late 2025; specific numbers will drift as Microsoft adds or removes tools, but the ratio of "tools you don't use" to "tools you actually call" stays roughly the same.

On a recent project planning session, I wanted to automate the creation of features, user stories, and tasks using this MCP server, and quickly realized I was burning more than a quarter of a 200k context window on tools I'd never use for this task (still meaningful even on a 1M window — that's room for the equivalent of a mid-sized codebase you're not planning to touch). I discovered this by running the /context command in Claude Code before even starting a conversation. It shows a visual breakdown of context usage, and the graph revealed just how much the Azure DevOps MCP server was consuming before I'd even added my codebase or documentation. This left less room for the actual work.

This guide shows you how I built a quick MCP proxy server using Node that filters the Azure DevOps server down to only the tools you need, cutting context consumption by up to 90% while maintaining full functionality.

The Context Window Problem

LLMs have finite context windows, and every token counts. Here's what the unfiltered Azure DevOps MCP server costs:

Total Tools: 70
Total Context Tokens: 55,125

Breakdown by Category:
├─ Core (3 tools): 2,098 tokens
├─ Work Iterations (3 tools): 2,259 tokens
├─ Work Items (19 tools): 14,377 tokens
├─ Build Pipeline (9 tools): 7,957 tokens
├─ Release Pipeline (2 tools): 2,400 tokens
├─ Repositories/PRs (18 tools): 13,767 tokens
├─ Wiki (5 tools): 3,589 tokens
├─ Test Plans (6 tools): 4,367 tokens
├─ Search (3 tools): 2,412 tokens
└─ Advanced Security (2 tools): 1,899 tokens

By filtering to just the core project tools, work item tracking, and search functionality, you can reduce this to approximately 25 tools consuming roughly 19,000 tokens - a 66% savings in context usage.

Architecture Overview

The proxy server architecture is straightforward:

Claude Code

MCP Proxy Server (filters tools)

Azure DevOps MCP Server (full tool set)

Azure DevOps API

The proxy acts as a transparent filter:

  1. Connects to the upstream Azure DevOps MCP server
  2. Retrieves the full tool list
  3. Filters to only desired tools (e.g., core**, wit*_, search__)
  4. Presents filtered tools to Claude
  5. Proxies tool execution requests through to upstream server

Implementation: Building the Proxy Server

Project Setup

Create a new Node.js project with the MCP SDK:

mkdir azure-devops-wit-proxy
cd azure-devops-wit-proxy
npm init -y
npm install @modelcontextprotocol/sdk

Update package.json to enable ES modules:

{
  "name": "azure-devops-wit-proxy",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1"
  }
}

Core Proxy Implementation

Create ado-wit-proxy.mjs:

import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import {
  ListToolsRequestSchema,
  CallToolRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'

// Configuration
const AZURE_DEVOPS_ORG = 'YOUR_ORG' // Replace with your organization name
const TOOL_PREFIX_FILTER = ['core_', 'wit_', 'search_']

// Create proxy server
const server = new Server(
  { name: 'azure-devops-wit-proxy', version: '1.0.0' },
  { capabilities: { tools: {} } },
)

// Connect to actual Azure DevOps MCP server
const client = new Client(
  { name: 'proxy-client', version: '1.0.0' },
  { capabilities: {} },
)

let filteredTools = []

// List tools - filter to only WIT tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools: filteredTools }
})

// Call tool - proxy through to actual server
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  return await client.callTool(request.params)
})

async function main() {
  // Connect to the real Azure DevOps server
  const isWindows = process.platform === 'win32'

  let command, args
  if (isWindows) {
    command = 'cmd.exe'
    args = ['/c', 'npx', '-y', '@azure-devops/mcp', AZURE_DEVOPS_ORG]
  } else {
    command = 'npx'
    args = ['-y', '@azure-devops/mcp', AZURE_DEVOPS_ORG]
  }

  const transport = new StdioClientTransport({
    command: command,
    args: args,
    env: process.env,
  })

  await client.connect(transport)

  // Get all tools and filter
  const result = await client.listTools()
  filteredTools = result.tools.filter((tool) =>
    TOOL_PREFIX_FILTER.some((prefix) => tool.name.startsWith(prefix)),
  )

  console.error(
    `Filtered to ${filteredTools.length} tools of ${result.tools.length} total tools.`,
  )

  // Start proxy server
  const serverTransport = new StdioServerTransport()
  await server.connect(serverTransport)
}

main().catch(console.error)

Configuration for Claude Code

Update your .claude.json to use the proxy:

{
  "mcpServers": {
    "azure-devops-wit": {
      "command": "node",
      "args": ["C:/path/to/azure-devops-wit-proxy/ado-wit-proxy.mjs"]
    }
  }
}

The proxy automatically forwards environment variables to the upstream server, so authentication works seamlessly.

Advanced Filtering Patterns

Multiple Filter Categories

You can extend the proxy to support multiple tool categories:

const TOOL_FILTERS = {
  wit: ['core_', 'wit_', 'search_'],
  repos: ['core_', 'repo_'],
  builds: ['core_', 'build_'],
}

// Get filter from environment variable or command line arg
const filterType = process.env.MCP_FILTER_TYPE || 'wit'
const TOOL_PREFIX_FILTER = TOOL_FILTERS[filterType]

filteredTools = result.tools.filter((tool) =>
  TOOL_PREFIX_FILTER.some((prefix) => tool.name.startsWith(prefix)),
)

Then configure which categories to enable:

{
  "mcpServers": {
    "azure-devops": {
      "command": "node",
      "args": ["C:/path/to/ado-wit-proxy.mjs"],
      "env": {
        "MCP_FILTER_TYPE": "wit"
      }
    }
  }
}

Tool Allowlist Configuration

For fine-grained control, implement an allowlist:

const ALLOWED_TOOLS = [
  'core_get_projects',
  'core_get_teams',
  'wit_my_work_items',
  'wit_get_work_item',
  'wit_update_work_item',
  'wit_create_work_item',
  'search_work_items',
]

filteredTools = result.tools.filter((tool) => ALLOWED_TOOLS.includes(tool.name))

This reduces to just 7 essential tools, further minimizing context usage.

Measuring the Impact

Let's compare context usage before and after implementing the proxy:

Before (Full Azure DevOps MCP Server)

Available Tools: 70
Context Tokens Used: 55,125 (~28% of a 200k window, ~5.5% of a 1M window)

After (Filtered Core + WIT + Search Tools)

Available Tools: 25
Context Tokens Used: 18,987 (~9.5% of a 200k window, ~1.9% of a 1M window)

Tokens Reclaimed: 36,138 — measurable on any context size, but most
impactful on smaller windows where you'd otherwise be paying overhead
on every turn.

Real-World Use Cases

Scenario 1: Sprint Planning Focus

A team using Azure DevOps primarily for sprint planning only needs:

const SPRINT_TOOLS = [
  'core_list_projects',
  'core_list_project_teams',
  'work_list_team_iterations',
  'wit_my_work_items',
  'wit_get_work_items_for_iteration',
  'wit_update_work_item',
  'wit_create_work_item',
]

Context Savings: 50,263 tokens (91% reduction) - Uses only 4,862 tokens

Scenario 2: Code Review Workflow

A team focused on pull request reviews needs:

const CODE_REVIEW_TOOLS = [
  'core_list_projects',
  'repo_list_repos_by_project',
  'repo_list_pull_requests_by_repo',
  'repo_get_pull_request_by_id',
  'repo_list_pull_request_threads',
  'repo_list_pull_request_thread_comments',
  'repo_create_pull_request_thread',
  'repo_reply_to_comment',
  'repo_resolve_comment',
]

Context Savings: 48,282 tokens (88% reduction) - Uses only 6,843 tokens

Scenario 3: Build Pipeline Management

A DevOps engineer managing builds needs:

const BUILD_TOOLS = [
  'core_list_projects',
  'pipelines_get_build_definitions',
  'pipelines_get_builds',
  'pipelines_get_build_status',
  'pipelines_get_build_log',
  'pipelines_run_pipeline',
  'pipelines_update_build_stage',
]

Context Savings: 49,722 tokens (90% reduction) - Uses only 5,403 tokens

Troubleshooting Common Issues

Proxy Doesn't Start

Symptom: No output when running the proxy script

Solutions:

  • Verify Node.js version (requires v20+, same as the Azure DevOps MCP server)
  • Check package.json has "type": "module"
  • Ensure MCP SDK is installed correctly
  • Try running with node --trace-warnings ado-wit-proxy.mjs

Tools Not Filtering

Symptom: Claude still sees all 70 tools

Solutions:

  • Verify filter prefix matches tool names exactly
  • Check console error output for filter results
  • Add debug logging: console.error('Filtering:', tool.name)

Authentication Failures

Symptom: Tools execute but return authorization errors

Solutions:

Reclaim Your Context Window

MCP tool bloat is a real problem when working with comprehensive servers like Azure DevOps.

By implementing a simple proxy like the one in this guide, you can reclaim thousands of tokens of context window space for what matters: your code, documentation, and productive conversations with your LLM of choice.

The pattern is universal - apply it to any MCP server that exposes more tools than you need, and optimize your AI development workflow for maximum efficiency.

Contact

Drop me a line. I read everything and reply within a day.

Required fields are marked “(required)”.