-->

dev ai, mcp, dotnet

In the previous post, you learned how to use AWS Knowledge MCP Server in Claude Code. That server was hosted remotely by AWS, making it simple to add with just a URL. But what if you need custom functionality specific to your domain or workflow? That’s where building your own MCP server comes in.

In this tutorial, you’ll build a custom Weather MCP Server using .NET 9. More importantly, you’ll implement it with two different transports: stdio (for local processes) and HTTP (for remote services). This demonstrates a key MCP principle: your business logic stays the same regardless of how clients connect to it.

What You’ll Build

A Weather MCP Server that provides:

  • Current weather information for any location
  • Random temperature generation (mock data for demonstration)
  • Two deployment options: local stdio and HTTP endpoint

By the end of this tutorial, you’ll understand:

  • How MCP servers are architected
  • The difference between business logic and transport layers
  • When to use stdio vs HTTP transports
  • How to integrate both types into Claude Code

Pre-requisites

Before starting, ensure you have:

Required Software

  1. .NET 9 SDK
  2. IDE or Code Editor
    • Visual Studio 2022 (Windows/Mac)
    • Visual Studio Code with C# extension
    • JetBrains Rider
    • Any text editor (vim, nano, etc.)
  3. Claude Code
    • Already installed from previous tutorials
    • Verify with: claude --version

Understanding MCP Server Architecture

Before diving into code, let’s understand how MCP servers are structured. This architectural understanding will make the implementation much clearer.

The Two-Layer Design

MCP servers follow a clean separation of concerns:

graph TB
    subgraph "MCP Server Architecture"
        A[Business Logic Layer] -->|Uses| B[MCP Protocol Layer]
        B -->|Exposes via| C1[Stdio Transport]
        B -->|Exposes via| C2[HTTP Transport]
        B -->|Exposes via| C3[SSE Transport]
    end

    C1 --> D1[Local Process<br/>Claude Code]
    C2 --> D2[Remote HTTP<br/>Any Client]
    C3 --> D2

    style A fill:#2ECC40
    style B fill:#FF851B
    style C1 fill:#0074D9
    style C2 fill:#0074D9
    style C3 fill:#0074D9

Layer 1: Business Logic

  • Your domain-specific code (weather data, database queries, API calls, etc.)
  • Independent of how clients connect
  • Reusable across different transports

Layer 2: Transport

  • Handles communication protocol (stdio, HTTP, SSE)
  • Wraps business logic in MCP protocol format
  • Manages serialization, requests, and responses

Why This Matters

With this design, you write your weather logic once and expose it through multiple transports. Need to add WebSocket support later? Just add another transport wrapper—no changes to business logic.

This is exactly what you’ll implement: one weather service, two transports.

Project Setup

Now you’re ready to start building!

Create a directory for your project:

mkdir weather-mcp-server
cd weather-mcp-server

Step 1: Create the Solution Structure

Let’s create a .NET solution with three projects:

# Create solution
dotnet new sln -n WeatherMcpServer

# Create shared library for business logic
dotnet new classlib -n WeatherMcp.Core

# Create stdio transport project
dotnet new console -n WeatherMcp.Stdio

# Create HTTP transport project
dotnet new web -n WeatherMcp.Http

# Add projects to solution
dotnet sln add WeatherMcp.Core/WeatherMcp.Core.csproj
dotnet sln add WeatherMcp.Stdio/WeatherMcp.Stdio.csproj
dotnet sln add WeatherMcp.Http/WeatherMcp.Http.csproj

# Add project references
dotnet add WeatherMcp.Stdio reference WeatherMcp.Core
dotnet add WeatherMcp.Http reference WeatherMcp.Core

Your folder structure should now look like this:

weather-mcp-server/
├── WeatherMcpServer.sln
├── WeatherMcp.Core/
│   ├── WeatherMcp.Core.csproj
│   └── Class1.cs
├── WeatherMcp.Stdio/
│   ├── WeatherMcp.Stdio.csproj
│   └── Program.cs
└── WeatherMcp.Http/
    ├── WeatherMcp.Http.csproj
    └── Program.cs

Step 2: Install Required NuGet Packages

You’ll implement the MCP protocol manually using JSON-RPC, giving you full control and understanding. No external MCP packages needed—just .NET built-in JSON serialization.

Step 3: Implement the Core Weather Service

Delete Class1.cs in WeatherMcp.Core and create these files:

WeatherMcp.Core/Models/WeatherData.cs

namespace WeatherMcp.Core.Models;

public record WeatherData(
    string Location,
    double TemperatureCelsius,
    string Condition,
    int Humidity,
    double WindSpeed,
    DateTime Timestamp
)
{
    public double TemperatureFahrenheit => (TemperatureCelsius * 9 / 5) + 32;
}

WeatherMcp.Core/Services/IWeatherService.cs

using WeatherMcp.Core.Models;

namespace WeatherMcp.Core.Services;

public interface IWeatherService
{
    Task<WeatherData> GetCurrentWeatherAsync(string location);
}

WeatherMcp.Core/Services/MockWeatherService.cs

using WeatherMcp.Core.Models;

namespace WeatherMcp.Core.Services;

public class MockWeatherService : IWeatherService
{
    private static readonly string[] Conditions =
    {
        "Sunny", "Partly Cloudy", "Cloudy", "Rainy", "Stormy", "Snowy", "Foggy", "Windy"
    };

    private readonly Random _random = new();

    public Task<WeatherData> GetCurrentWeatherAsync(string location)
    {
        // Generate random but realistic weather data
        var temperature = _random.Next(-10, 40); // -10°C to 40°C
        var condition = Conditions[_random.Next(Conditions.Length)];
        var humidity = _random.Next(30, 100);
        var windSpeed = _random.NextDouble() * 30; // 0-30 km/h

        var weather = new WeatherData(
            Location: location,
            TemperatureCelsius: temperature,
            Condition: condition,
            Humidity: humidity,
            WindSpeed: Math.Round(windSpeed, 1),
            Timestamp: DateTime.UtcNow
        );

        return Task.FromResult(weather);
    }
}

WeatherMcp.Core/Mcp/McpServer.cs

This is the core MCP protocol implementation:

using System.Text.Json;
using System.Text.Json.Serialization;
using WeatherMcp.Core.Services;

namespace WeatherMcp.Core.Mcp;

public class McpServer
{
    private readonly IWeatherService _weatherService;

    public McpServer(IWeatherService weatherService)
    {
        _weatherService = weatherService;
    }

    public async Task<string> HandleRequestAsync(string jsonRequest)
    {
        try
        {
            var request = JsonSerializer.Deserialize<McpRequest>(jsonRequest);

            if (request == null)
                return CreateErrorResponse("Invalid request format");

            return request.Method switch
            {
                "initialize" => await HandleInitializeAsync(request),
                "tools/list" => HandleToolsList(request),
                "tools/call" => await HandleToolCallAsync(request),
                _ => CreateErrorResponse($"Unknown method: {request.Method}")
            };
        }
        catch (Exception ex)
        {
            return CreateErrorResponse($"Error processing request: {ex.Message}");
        }
    }

    private Task<string> HandleInitializeAsync(McpRequest request)
    {
        var response = new
        {
            jsonrpc = "2.0",
            id = request.Id,
            result = new
            {
                protocolVersion = "2024-11-05",
                capabilities = new
                {
                    tools = new { }
                },
                serverInfo = new
                {
                    name = "weather-mcp-server",
                    version = "1.0.0"
                }
            }
        };

        return Task.FromResult(JsonSerializer.Serialize(response));
    }

    private string HandleToolsList(McpRequest request)
    {
        var response = new
        {
            jsonrpc = "2.0",
            id = request.Id,
            result = new
            {
                tools = new[]
                {
                    new
                    {
                        name = "get_current_weather",
                        description = "Get current weather information for a specific location",
                        inputSchema = new
                        {
                            type = "object",
                            properties = new
                            {
                                location = new
                                {
                                    type = "string",
                                    description = "City name or location (e.g., 'London', 'New York', 'Tokyo')"
                                }
                            },
                            required = new[] { "location" }
                        }
                    }
                }
            }
        };

        return JsonSerializer.Serialize(response);
    }

    private async Task<string> HandleToolCallAsync(McpRequest request)
    {
        if (request.Params?.Name != "get_current_weather")
            return CreateErrorResponse("Unknown tool");

        var location = request.Params.Arguments?.GetProperty("location").GetString();

        if (string.IsNullOrEmpty(location))
            return CreateErrorResponse("Location is required");

        var weather = await _weatherService.GetCurrentWeatherAsync(location);

        var response = new
        {
            jsonrpc = "2.0",
            id = request.Id,
            result = new
            {
                content = new[]
                {
                    new
                    {
                        type = "text",
                        text = $@"Current weather in {weather.Location}:
Temperature: {weather.TemperatureCelsius}°C ({weather.TemperatureFahrenheit:F1}°F)
Condition: {weather.Condition}
Humidity: {weather.Humidity}%
Wind Speed: {weather.WindSpeed} km/h
Last Updated: {weather.Timestamp:yyyy-MM-dd HH:mm:ss} UTC"
                    }
                }
            }
        };

        return JsonSerializer.Serialize(response);
    }

    private string CreateErrorResponse(string message)
    {
        var response = new
        {
            jsonrpc = "2.0",
            error = new
            {
                code = -32603,
                message
            }
        };

        return JsonSerializer.Serialize(response);
    }
}

public class McpRequest
{
    [JsonPropertyName("jsonrpc")]
    public string? JsonRpc { get; set; }

    [JsonPropertyName("id")]
    public object? Id { get; set; }

    [JsonPropertyName("method")]
    public string? Method { get; set; }

    [JsonPropertyName("params")]
    public McpParams? Params { get; set; }
}

public class McpParams
{
    [JsonPropertyName("name")]
    public string? Name { get; set; }

    [JsonPropertyName("arguments")]
    public JsonElement? Arguments { get; set; }
}

Understanding the MCP Protocol Implementation

This code is the heart of your MCP server. Let’s break down what each part does:

The Main Handler: HandleRequestAsync

public async Task<string> HandleRequestAsync(string jsonRequest)

This is the entry point for all MCP requests. It:

  1. Deserializes the incoming JSON-RPC request
  2. Routes to the appropriate handler based on the method field
  3. Returns a JSON-RPC response

The MCP protocol uses three core methods:

  • initialize - Handshake when client connects
  • tools/list - Client asks “what can you do?”
  • tools/call - Client executes a specific tool

HandleInitializeAsync: The Handshake

private Task<string> HandleInitializeAsync(McpRequest request)

When a client first connects, they send an initialize request. Your response tells them:

  • Protocol version: 2024-11-05 (current MCP spec version)
  • Capabilities: What features you support (in this case, just tools)
  • Server info: Your server’s name and version

This is like a handshake - the client learns what your server can do before making any requests.

HandleToolsList: Advertising Your Tools

private string HandleToolsList(McpRequest request)

This is where you advertise your tool to clients. Remember from the AWS MCP post how the AI knew what tool to use? This is where that magic happens!

Each tool advertisement includes:

  • name: The tool identifier (get_current_weather)
  • description: What the tool does (this is what the AI reads!)
  • inputSchema: JSON Schema defining required parameters

The description is crucial - it’s how the AI decides when to use your tool. Make it clear and specific!

HandleToolCallAsync: Doing the Work

private async Task<string> HandleToolCallAsync(McpRequest request)

When the AI decides to use your tool, this method:

  1. Validates the tool name matches (get_current_weather)
  2. Extracts the location parameter from the request
  3. Calls your weather service to get the data
  4. Formats the response according to MCP spec

The response must follow the MCP format:

{
  "jsonrpc": "2.0",
  "id": <request_id>,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "<your formatted response>"
      }
    ]
  }
}

The content array can contain multiple items (text, images, resources, etc.). For simplicity, we’re just returning formatted text.

Error Handling

private string CreateErrorResponse(string message)

When something goes wrong (invalid tool, missing parameter, etc.), you return a JSON-RPC error response with:

  • code: -32603 (Internal error code from JSON-RPC spec)
  • message: What went wrong

The Request Models

public class McpRequest
public class McpParams

These classes map JSON-RPC requests to C# objects. Key points:

  • JsonPropertyName attributes map JSON field names to C# properties
  • JsonElement? for Arguments because we don’t know the structure ahead of time
  • Nullable types (string?, object?) handle optional fields

How It All Flows Together

Here’s a typical conversation:

  1. Client connects
    → {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
    ← {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05",...}}
    
  2. Client asks what tools exist
    → {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
    ← {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"get_current_weather",...}]}}
    
  3. AI reads tool descriptions and decides to call your tool
    → {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_current_weather","arguments":{"location":"London"}}}
    ← {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"Current weather in London..."}]}}
    

This is JSON-RPC over MCP - your transport layer (stdio or HTTP) handles the communication, while this class handles the protocol logic.

The core business logic is now complete! You now have:

  • Weather data models
  • Mock weather service
  • MCP protocol handler

Step 4: Implement Stdio Transport

Now wrap your weather service in a stdio transport. Replace the contents of WeatherMcp.Stdio/Program.cs:

using System.Text;
using WeatherMcp.Core.Mcp;
using WeatherMcp.Core.Services;

// Create weather service and MCP server
var weatherService = new MockWeatherService();
var mcpServer = new McpServer(weatherService);

// Read from stdin, write to stdout (stdio transport)
// IMPORTANT: Use UTF8 without BOM to avoid JSON parsing issues
var utf8NoBom = new UTF8Encoding(false);
using var reader = new StreamReader(Console.OpenStandardInput(), utf8NoBom);
using var writer = new StreamWriter(Console.OpenStandardOutput(), utf8NoBom) { AutoFlush = true };

try
{
    while (true)
    {
        var line = await reader.ReadLineAsync();

        if (line == null)
            break; // EOF reached

        if (string.IsNullOrWhiteSpace(line))
            continue;

        var response = await mcpServer.HandleRequestAsync(line);

        await writer.WriteLineAsync(response);
    }
}
catch (Exception)
{
    // Silently handle errors - stderr logging can interfere with Claude Code
}

Important Notes:

  1. UTF-8 without BOM: The new UTF8Encoding(false) is critical. By default, .NET’s UTF-8 encoding includes a Byte Order Mark (BOM) which breaks JSON parsing. Claude Code expects pure JSON output without any BOM.

  2. No stderr logging: We removed all Console.Error logging because Claude Code monitors stderr for errors. Any output to stderr can cause connection issues or timeouts.

  3. Silent error handling: Errors are caught but not logged. In production, you might want to log to a file instead.

Test the Stdio Server

Build and test the stdio server:

# Build the project
dotnet build WeatherMcp.Stdio

# Test it manually
cd WeatherMcp.Stdio/bin/Debug/net9.0

# Run and send a test request
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | ./WeatherMcp.Stdio

You should see the initialization response!

Step 5: Implement HTTP Transport

Now create an HTTP endpoint. Replace WeatherMcp.Http/Program.cs:

using WeatherMcp.Core.Mcp;
using WeatherMcp.Core.Services;

var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddSingleton<IWeatherService, MockWeatherService>();
builder.Services.AddSingleton<McpServer>();

// Configure CORS for development
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.AllowAnyOrigin()
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

var app = builder.Build();

app.UseCors();

// MCP endpoint
app.MapPost("/mcp", async (HttpContext context, McpServer mcpServer) =>
{
    using var reader = new StreamReader(context.Request.Body);
    var requestBody = await reader.ReadToEndAsync();

    var response = await mcpServer.HandleRequestAsync(requestBody);

    context.Response.ContentType = "application/json";
    await context.Response.WriteAsync(response);
});

// Health check endpoint
app.MapGet("/health", () => Results.Ok(new { status = "healthy", service = "weather-mcp-server" }));

// Root endpoint with instructions
app.MapGet("/", () => Results.Ok(new
{
    message = "Weather MCP Server",
    version = "1.0.0",
    endpoints = new
    {
        mcp = "/mcp (POST)",
        health = "/health (GET)"
    },
    example = "POST to /mcp with MCP JSON-RPC requests"
}));

app.Run("http://localhost:3000");

Test the HTTP Server

# Build and run
cd WeatherMcp.Http
dotnet run

In another terminal, test it:

# Test health endpoint
curl http://localhost:3000/health

# Test MCP initialization
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'

# Test weather tool
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_current_weather","arguments":{"location":"London"}}}'

Once you’ve ensured both transports are working, move on to the next step.

Step 6: Add Stdio Server to Claude Code

Now integrate the stdio version with Claude Code.

Build a Release Version

cd weather-mcp-server
dotnet publish WeatherMcp.Stdio -c Release -o ./publish/stdio

This creates a .dll file that you’ll run using the dotnet command.

Why .dll instead of standalone executable? By default, dotnet publish creates a framework-dependent deployment (.dll file) that requires the .NET runtime. This is the standard approach most .NET developers are familiar with. It’s smaller, faster to publish, and leverages the .NET SDK already installed on your machine.

Add to Claude Code

Since .NET publish creates a .dll (not a standalone executable), you need to configure Claude Code to run it with the dotnet command.

Option 1: Using Claude Code CLI

Unfortunately, claude mcp add doesn’t support complex command structures like dotnet <dll>, so you’ll need to configure it manually.

Option 2: Manual Configuration (Recommended)

Edit your user-scoped config file:

nano ~/.claude.json

Add the weather-stdio server to the mcpServers section:

{
  "mcpServers": {
    "aws-knowledge": {
      "type": "http",
      "url": "https://knowledge-mcp.global.api.aws"
    },
    "weather-stdio": {
      "type": "stdio",
      "command": "dotnet",
      "args": ["/absolute/path/to/weather-mcp-server/publish/stdio/WeatherMcp.Stdio.dll"],
      "env": {}
    }
  }
}

Important: Replace /absolute/path/to/ with your actual path. To get it:

cd weather-mcp-server/publish/stdio
pwd

For example, it might look like:

"weather-stdio": {
  "type": "stdio",
  "command": "dotnet",
  "args": ["/Users/yourname/weather-mcp-server/publish/stdio/WeatherMcp.Stdio.dll"],
  "env": {}
}

Save and exit (Ctrl+X, then Y, then Enter in nano).

Verify It Works

# Check MCP servers
claude mcp list

# Should show: weather-stdio - ✓ Connected

Start Claude Code and ask:

What's the weather in Sydney?

Claude Code should use your custom weather MCP server!

Step 7: Add HTTP Server to Claude Code

Now add the HTTP version. Since both stdio and HTTP servers provide the same tool (get_current_weather), we’ll temporarily remove the stdio server to clearly test the HTTP version.

Remove Stdio Server

Edit ~/.claude.json and comment out or remove the weather-stdio entry:

nano ~/.claude.json

Remove or comment out the weather-stdio section so it looks like this:

{
  "mcpServers": {
    "aws-knowledge": {
      "type": "http",
      "url": "https://knowledge-mcp.global.api.aws"
    }
  }
}

Save and exit (Ctrl+X, then Y, then Enter).

Verify it’s removed:

claude mcp list

You should only see aws-knowledge now.

Start the HTTP Server

In one terminal:

cd WeatherMcp.Http
dotnet run

This runs on http://localhost:3000.

Add to Claude Code

In another terminal:

claude mcp add --transport http weather-http --scope user http://localhost:3000/mcp

The --scope user flag makes the HTTP server available globally, just like the stdio version.

Alternative: Manual Configuration

If configuring manually, edit ~/.claude.json:

{
  "mcpServers": {
    "weather-stdio": {
      "type": "stdio",
      "command": "dotnet",
      "args": ["/absolute/path/to/WeatherMcp.Stdio.dll"],
      "env": {}
    },
    "weather-http": {
      "type": "http",
      "url": "http://localhost:3000/mcp"
    }
  }
}

Verify Both Work

claude mcp list

# Should show:
# weather-stdio - ✓ Connected
# weather-http - ✓ Connected

Now you have the same weather service exposed through two different transports!

When to Use Which Transport

Now that you’ve built both, when should you use each?

Use Stdio Transport When:

Local development and testing

  • Fast iteration, no network overhead
  • Easy debugging with stderr logs
  • Direct process communication

Personal tools and scripts

  • No need for remote access
  • Simpler deployment (single executable)
  • Lower resource usage

Security-sensitive operations

  • Data never leaves your machine
  • No network exposure
  • Direct file system access

Example Use Cases:

  • Local file system MCP server
  • Database query tools
  • Personal automation scripts
  • Development utilities

Use HTTP Transport When:

Team collaboration

  • Multiple developers share one server
  • Centralized data source
  • Consistent results across team

Remote services

  • MCP server on different machine
  • Cloud-hosted services
  • Containerized deployments

Third-party integrations

  • Public APIs need HTTP
  • Integration with existing web services
  • Cross-platform compatibility

Scalability requirements

  • Load balancing
  • Multiple instances
  • High availability

Example Use Cases:

  • Shared company knowledge base
  • Cloud-based data services
  • External API wrappers
  • Production services

Comparison Table

Feature Stdio HTTP
Setup Complexity Simple Moderate
Network Required No Yes
Remote Access No Yes
Performance Fastest Network latency
Security Local only Authentication needed
Scalability One instance Load balanceable
Debugging Direct logs HTTP tools/logging
Best For Local tools Shared services

Conclusion

Congratulations! You’ve built a complete custom MCP server from scratch using .NET 9. More importantly, you’ve learned the fundamental architectural principle of MCP: separation of business logic from transport.

If you didn’t follow along, you can download the source code of the final version here (the project is in the weather-mcp-server folder)

What You’ve Accomplished

  1. Built a reusable weather service - Core business logic that’s transport-agnostic
  2. Implemented stdio transport - For fast local development and personal tools
  3. Implemented HTTP transport - For remote access and team collaboration
  4. Integrated with Claude Code - Made your custom tools accessible to AI
  5. Understood when to use each - Made informed architecture decisions

Key Takeaways

  • MCP is transport-agnostic - Write once, deploy many ways
  • Stdio is fast and simple - Perfect for local tools
  • HTTP enables sharing - Ideal for team services
  • Protocol is straightforward - JSON-RPC makes it easy to implement
  • Custom servers unlock potential - AI can access your unique data and services

Next Steps

Now that you understand the fundamentals, consider:

  1. Add real data sources - Replace mock weather with actual APIs
  2. Implement authentication - Secure your HTTP server
  3. Add more tools - Forecast, historical data, alerts
  4. Deploy to production - Docker, Kubernetes, cloud platforms
  5. Build domain-specific servers - Database tools, CI/CD integrations, business logic

The MCP ecosystem is growing rapidly. By understanding how to build custom servers, you can create AI-accessible tools tailored to your specific needs.

Resources

dev ai, mcp, aws, claude-code

In the previous post, we took a detailed look at Model Context Protocol (MCP). Now it’s time to work on something more practical and utilize that knowledge. AWS has released a comprehensive suite of MCP servers, and since AWS is a ubiquitous cloud provider, I thought it would be a good exercise to integrate them into my workflow when using Claude Code.

AWS provides over 60 MCP servers covering everything from documentation and infrastructure management to AI/ML services and cost analysis. In this tutorial, we’ll focus on the AWS Knowledge MCP Server which recently became generally available.

What is AWS Knowledge MCP Server?

AWS Knowledge MCP Server is a fully managed remote service that provides AI agents and MCP clients with instant access to authoritative AWS information in LLM-compatible format. Think of it as having AWS’s entire knowledge base—documentation, API references, blog posts, What’s New announcements, and Well-Architected best practices—directly accessible to your AI assistant.

Key Features

Comprehensive AWS Knowledge Access:

  • Official AWS documentation
  • API references for all AWS services
  • What’s New announcements
  • Well-Architected Framework best practices
  • AWS Builder Library content
  • AWS blog posts
  • Regional availability of AWS APIs and CloudFormation resources

Fully Managed:

  • No setup or infrastructure management required
  • Publicly accessible at no cost
  • No AWS account required
  • Available globally
  • Subject to rate limits

Benefits:

  1. Accuracy - AI responses anchored in trusted AWS context
  2. Consistency - Reliable execution based on official guidance
  3. Efficiency - No manual context management or copy-pasting from docs

Setting Up AWS Knowledge MCP Server in Claude Code

Before you start, to get a baseline, run the following command to see what MCP servers you have configured. By default you shouldn’t see anything.

claude mcp list

In a default Claude Code installation, your output should look similar to this:

Now, proceed to add the AWS Knowledge MCP Server.

Adding the Server

One of the beauties of AWS Knowledge MCP Server is its simplicity—it’s a remote HTTP service that requires minimal configuration.

First, exit Claude Code by running the following command in the terminal:

exit

Then, use the Claude Code CLI to add the server:

claude mcp add --transport http aws-knowledge https://knowledge-mcp.global.api.aws

This adds the server to your local scope (project-specific). If you want it available across all your projects:

claude mcp add --transport http aws-knowledge https://knowledge-mcp.global.api.aws --scope user

Alternative: Manual Configuration

You can also add it directly to your project’s .mcp.json file:

{
  "mcpServers": {
    "aws-knowledge": {
      "type": "http",
      "url": "https://knowledge-mcp.global.api.aws"
    }
  }
}

Verifying the Connection

Check that the server is properly configured:

claude mcp list

Your output should look like this:

Checking MCP server health...

aws-knowledge: https://knowledge-mcp.global.api.aws (HTTP) - ✓ Connected

You can also verify within Claude Code using the /mcp command.

Testing the Connection

To test that the MCP server is actually being used by Claude Code, ask an AWS-related question such as:

What are the current AWS Lambda timeout limits?

And the output should show that Claude Code knows what tool to use to get this data:

And when you proceed, you should get the answer directly coming from the AWS MCP Server:

How Does Claude Know What Tool to Use?

It’s great that when you ask an AWS-related question, it goes straight to the AWS Knowledge MCP server and gets the answer from the correct resource. But how does it know where to find the correct answer? What if I have an Azure MCP server installed as well, or other tools? How would it know which one to choose?

The MCP Discovery Process

When an MCP server connects to Claude Code, it doesn’t just sit there waiting. It actively advertises its capabilities through the Model Context Protocol. This is called the “capability discovery” phase, and it happens automatically when the server is initialized.

Here’s what happens behind the scenes:

sequenceDiagram
    participant CC as Claude Code
    participant MCP as MCP Server<br/>(AWS Knowledge)
    participant AI as AI Assistant

    CC->>MCP: Initialize connection
    MCP->>CC: Server info & capabilities
    Note over MCP,CC: Server advertises:<br/>- Available tools<br/>- Tool descriptions<br/>- Required parameters
    CC->>MCP: Request tool list
    MCP->>CC: Tools: search_documentation,<br/>read_documentation,<br/>get_regional_availability, etc.

    Note over CC,AI: User asks AWS question

    CC->>AI: Available tools from all MCP servers
    AI->>AI: Analyze question & match tools
    AI->>CC: Use aws___search_documentation
    CC->>MCP: Execute search_documentation("Lambda timeout")
    MCP->>CC: Return AWS documentation results
    CC->>AI: Results from AWS Knowledge
    AI->>CC: Formatted response to user

Tool Descriptions: The Key to Smart Routing

Each MCP server provides detailed metadata about its tools. This metadata is what allows the AI to make intelligent routing decisions. Let’s look at real examples:

AWS Knowledge MCP Server advertises:

{
  "name": "aws___search_documentation",
  "description": "Search AWS documentation, blogs, What's New announcements, and technical guides for information about AWS services, features, and best practices",
  "inputSchema": {
    "type": "object",
    "properties": {
      "search_phrase": {
        "type": "string",
        "description": "Search query for AWS documentation"
      },
      "limit": {
        "type": "integer",
        "description": "Maximum number of results to return"
      }
    }
  }
}

A hypothetical Weather MCP Server would advertise:

{
  "name": "weather___get_forecast",
  "description": "Get weather forecast for a specific location including temperature, precipitation, and conditions for upcoming days",
  "inputSchema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City name or coordinates"
      },
      "days": {
        "type": "integer",
        "description": "Number of days to forecast (1-10)"
      }
    }
  }
}

Notice how different these descriptions are? The AI uses these descriptions to understand what each tool does and when to use it.

The Decision-Making Process

When you ask a question, here’s how the AI decides which tool to use:

graph TD
    A[User asks:<br/>'What are Lambda timeout limits?'] --> B[AI analyzes question]
    B --> C{Check all available<br/>MCP tools}

    C --> D1[aws___search_documentation<br/>AWS docs, services, features]
    C --> D2[weather___get_forecast<br/>Weather data for locations]
    C --> D3[github___search_code<br/>Search code repositories]

    B --> E{Match question to<br/>tool descriptions}

    E --> F{Keywords:<br/>Lambda, timeout, limits}
    F --> G{Best match?}

    G -->|AWS service| H[✓ aws___search_documentation]
    G -->|Weather related| I[✗ weather___get_forecast]
    G -->|Code related| J[✗ github___search_code]

    H --> K[Execute AWS Knowledge query]
    K --> L[Return Lambda timeout documentation]

    style H fill:#2ECC40
    style I fill:#FF4136
    style J fill:#FF4136

What Makes This Work

The AI considers several factors when choosing a tool:

  1. Semantic Matching: Does the tool description align with the question’s intent?
    • “Lambda timeout limits” semantically matches “AWS services, features, and best practices”
    • It does NOT match “weather forecast” or “code repositories”
  2. Domain Context: What domain does the question belong to?
    • AWS services → AWS Knowledge MCP Server
    • Weather information → Weather MCP Server
    • Code search → GitHub MCP Server
  3. Parameter Compatibility: Can the question be translated into the tool’s required parameters?
    • “Lambda timeout limits” → search_phrase: "Lambda timeout limits"
    • This fits the AWS Knowledge schema perfectly
  4. Tool Specificity: More specific tool descriptions get priority
    • A tool described as “AWS Lambda documentation” would beat “General cloud documentation”
    • Specificity helps avoid ambiguity

Multiple MCP Servers Working Together

The beauty of this system is that you can have many MCP servers installed simultaneously, and the AI will route questions to the right one:

graph LR
    A[You ask questions] --> B[AI Assistant]

    B --> C{Analyze & Route}

    C -->|AWS questions| D[AWS Knowledge<br/>MCP Server]
    C -->|Weather queries| E[Weather<br/>MCP Server]
    C -->|GitHub searches| F[GitHub<br/>MCP Server]
    C -->|Database queries| G[PostgreSQL<br/>MCP Server]

    D --> H[AWS Documentation]
    E --> I[Weather API]
    F --> J[GitHub API]
    G --> K[Your Database]

    style D fill:#FF851B
    style E fill:#0074D9
    style F fill:#2ECC40
    style G fill:#B10DC9

Example conversation with multiple servers:

You: What's the weather in Sydney?
AI: [Routes to Weather MCP Server] → Returns forecast

You: Does Lambda support ARM64 in ap-southeast-2?
AI: [Routes to AWS Knowledge MCP Server] → Returns AWS docs

You: Show me my S3 buckets
AI: [Routes to AWS API MCP Server] → Lists actual buckets

You: Find TypeScript code that uses Redis
AI: [Routes to GitHub MCP Server] → Searches repositories

Each question automatically goes to the right server based on the tool descriptions advertised during the MCP handshake.

Writing Good Tool Descriptions

When building your own MCP server, the quality of your tool descriptions directly impacts how effectively the AI can use your tools. Good descriptions should:

  1. Be specific: “Search AWS Lambda documentation” beats “Search docs”
  2. Include domain keywords: Mention the service, technology, or domain
  3. Explain the purpose: What problem does this tool solve?
  4. Define clear parameters: What inputs does the tool need?

This is why AWS Knowledge MCP Server works so well—its tool descriptions are comprehensive, specific, and well-aligned with the questions developers actually ask about AWS.

Conclusion

The AWS Knowledge MCP Server transforms how you access AWS documentation in Claude Code. With instant access to up-to-date AWS information, you can work faster, learn continuously, and make better-informed decisions—all without leaving your development environment.

The MCP standard ensures these integrations are reliable, secure, and maintainable. As AWS continues to add more specialized servers, the ecosystem will only become more powerful.

In this post, we looked into a rather simple MCP server that is hosted remotely. In the upcoming posts, we will delve deeper into custom MCP servers and MCP servers that run on our dev environments.

Resources

dev ai, mcp, llm

Introduction

Artificial intelligence models are powerful, but they exist in a vacuum. They have knowledge up to their training cutoff date and can only work with information fed directly into a conversation. What happens when you need an AI to access real-time data, query your company’s database, or control devices in your home lab? This is where the Model Context Protocol (MCP) comes in.

MCP is an open standard that acts as a bridge between AI models and external systems. Think of it as a universal translator that allows AI assistants to securely request data from APIs, databases, file systems, and other services without requiring custom integrations for each one. In this post, we’ll explore what MCP is, how it works, and why it matters for the future of AI applications.

What Is the Model Context Protocol?

At its core, MCP is a standardized specification for how AI models can communicate with external data sources and tools. Instead of building custom code for each integration, developers can create MCP “servers” that expose their tools, databases, or APIs in a way that any MCP-compatible model can understand and use.

The key innovation is standardization. Without MCP, every time you wanted an AI model to access a new service, you’d need to describe that service’s API in detail within your prompt or system instructions. The model would then have to figure out how to call the right endpoints with the right parameters. It’s fragile, prone to errors, and doesn’t scale well.

MCP solves this by defining a clear protocol. An MCP server advertises what it can do—what tools it offers, what parameters they take, and what they return. The AI model receives this information in a structured format, understands it automatically, and can reliably invoke the right operations.

graph LR
    A[AI Model<br/>Assistant] <--> B[MCP Protocol<br/>Standardized Interface]
    B <--> C[MCP Server<br/>Database]
    B <--> D[MCP Server<br/>API]
    B <--> E[MCP Server<br/>File System]
    B <--> F[MCP Server<br/>IoT Devices]

    style A fill:#2ECC40
    style B fill:#FF851B
    style C fill:#0074D9
    style D fill:#0074D9
    style E fill:#0074D9
    style F fill:#0074D9

How MCP Works: The Simple Version

Imagine you have a restaurant reservation database and want an AI assistant to help customers check availability. Here’s the flow:

  1. You build an MCP server that exposes a tool called “check_availability” with parameters for date, time, and party size.

  2. The MCP server defines this in a standard schema that clearly states: “This tool requires a date (string), a time (string), and party_size (number).”

  3. When a user asks the AI “Do you have availability for 4 people tomorrow at 7 PM?”, the AI receives the MCP server’s capability list, understands what tools are available, and calls the check_availability tool with the right parameters.

  4. The MCP server processes the request, queries your database, and returns the result to the AI.

  5. The AI presents the answer to the user: “Yes, we have a table available.”

This entire flow is standardized. The AI doesn’t need custom code or special prompting—it just follows the protocol.

sequenceDiagram
    participant User
    participant AI as AI Assistant
    participant MCP as MCP Server<br/>(Restaurant DB)
    participant DB as Database

    User->>AI: "Table for 4 tomorrow at 7 PM?"
    AI->>MCP: Get available tools
    MCP->>AI: Tools: check_availability(date, time, party_size)
    AI->>MCP: check_availability("2025-10-28", "19:00", 4)
    MCP->>DB: Query availability
    DB->>MCP: Available: Yes
    MCP->>AI: Result: Table available
    AI->>User: "Yes, we have a table available."

    Note over AI,MCP: Standardized MCP Protocol

Real-Life Usage Scenarios

Scenario 1: Corporate Data Access A financial analyst asks their AI assistant, “What was our revenue growth in Q3?” Instead of manually pulling data, the AI calls an MCP server that queries the company’s data warehouse. The result is real, current, and accurate. The analyst gets instant answers without leaving the chat interface.

Scenario 2: Home Automation You have smart lights, thermostats, and security cameras in your home lab. An MCP server exposes these as tools. You can ask your AI assistant, “What’s the current temperature?” or “Turn on the lights in the living room,” and the AI reliably interacts with your devices through the MCP server.

Scenario 3: Knowledge Base Integration A support team uses MCP to connect their AI assistant to the internal knowledge base. When helping customers, the AI can search company documentation, policies, and FAQs in real-time. It provides accurate, consistent answers based on actual company knowledge, not hallucinations.

Scenario 4: DevOps and Monitoring An MCP server exposes system monitoring tools—check CPU usage, disk space, running processes, recent errors. An engineer asks their AI assistant, “What’s wrong with the production server?” The AI queries the monitoring tools, analyzes the data, and provides troubleshooting suggestions.

graph LR
    A[AI<br/>Assistant] --> B[MCP Protocol]

    B --> C1[Enterprise:<br/>Data Warehouses,<br/>Knowledge Bases,<br/>CRM Systems]

    B --> C2[Home & Personal:<br/>Smart Devices,<br/>Files,<br/>Media Libraries]

    B --> C3[Development:<br/>Code Repos,<br/>Build Systems,<br/>Package Managers]

    B --> C4[Operations:<br/>Monitoring,<br/>Logs,<br/>Infrastructure]

    style A fill:#2ECC40
    style B fill:#FF851B
    style C1 fill:#0074D9
    style C2 fill:#0074D9
    style C3 fill:#0074D9
    style C4 fill:#0074D9

Why MCP Matters

Without MCP, integrating external systems with AI requires constant manual effort. You write custom code, describe it to the model in prompts, and hope the model interprets your instructions correctly. This approach is error-prone and doesn’t scale.

MCP enables a future where tools and data sources are AI-ready by default. Developers expose their systems through MCP, and any AI model that understands the protocol can use them reliably. It’s a win for security too—you define exactly what operations are allowed and how they should be executed, rather than trusting an AI to interpret natural language instructions.

Conclusion

The Model Context Protocol represents a fundamental shift in how AI systems interact with the real world. By standardizing the interface between models and external systems, MCP makes AI more practical, reliable, and powerful. Whether you’re building enterprise applications, home automation systems, or experimental tools, MCP provides a framework for safely and reliably connecting your data and services to AI.

As MCP adoption grows, we can expect to see a thriving ecosystem of servers and integrations. For developers, the takeaway is clear: if you have data or tools you want AI systems to access, MCP is the modern way to do it. For users, it means AI models will become increasingly useful as practical assistants that can access real information and perform real actions—not just generate text based on training data.

Resources