2026-09-18

[C#] .NET 10 MCP Authentication:讓 OpenCode 使用 Bearer Token 連 ASP.NET MCP Server

上一篇已經讓 .NET 10 ASP.NET Core 支援 MCP,OpenCode 也可以直接呼叫 UpdateHoroscope 去修改網站上的雙子座運勢

不過上一篇是直接連 http://localhost:5100/mcp

本機測試當然沒差,但如果真的放到 Server 上,這樣我自己是不太敢用

因為現在這個 MCP Tool 不是只能查資料,它是真的可以修改 Server Data

所以這篇就補一個最簡單的 Bearer Token,先不要扯 OAuth,單純做到 Token 不對就不能使用 MCP

OpenCode
↓
Bearer Token
↓
ASP.NET Core MCP
↓
UpdateHoroscope

Program.cs 加入 Bearer Token

上一篇的 HoroscopeServiceHoroscopeTools 都不用改

這次直接改 Program.cs 就好

using System.Net;
using McpHoroscope;
using ModelContextProtocol.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<HoroscopeService>();

builder.Services
    .AddMcpServer()
    .WithHttpTransport(options =>
    {
        // 這篇沒有要保存 MCP Session
        // 先用 Stateless 就好
        options.SessionMode =
            HttpServerSessionMode.Stateless;
    })
    .WithTools<HoroscopeTools>();

var app = builder.Build();

// 這篇只是測試,所以先直接寫死比較快
// 正式環境不要真的這樣放
const string mcpApiKey =
    "my-mcp-secret-key";

app.Use(
    async (context, next) =>
    {
        // 一般網站不用驗證
        // 只有 /mcp 進來才檢查 Token
        if (context.Request.Path.StartsWithSegments("/mcp"))
        {
            var authorization =
                context.Request.Headers.Authorization.ToString();

            var expected =
                $"Bearer {mcpApiKey}";

            // Token 不對就直接擋掉
            // 不要讓 Request 繼續跑到 MCP Tool
            if (authorization != expected)
            {
                context.Response.StatusCode =
                    StatusCodes.Status401Unauthorized;

                return;
            }
        }

        await next();
    });

app.MapGet(
    "/",
    (HoroscopeService horoscopeService) =>
    {
        var horoscope =
            horoscopeService.Get();

        var content =
            WebUtility.HtmlEncode(
                horoscope.Content);

        var updatedAt =
            horoscope.UpdatedAt is null
                ? "尚未更新"
                : horoscope.UpdatedAt.Value
                    .ToString("yyyy-MM-dd HH:mm:ss");

        // 這篇只是測 MCP
        // 頁面先直接放在這裡,不另外拆 Razor
        var html = $$"""
        <!DOCTYPE html>
        <html lang="zh-Hant">
        <head>
            <meta charset="utf-8">
            <title>雙子座今日運勢</title>

            <style>
                body {
                    font-family: Arial, sans-serif;
                    max-width: 720px;
                    margin: 80px auto;
                    padding: 0 20px;
                    line-height: 1.8;
                }

                .card {
                    padding: 30px;
                    border: 1px solid #ddd;
                    border-radius: 12px;
                }

                .time {
                    margin-top: 20px;
                    color: #777;
                    font-size: 14px;
                }
            </style>
        </head>

        <body>
            <div class="card">
                <h1>雙子座今日運勢</h1>

                <p>{{content}}</p>

                <div class="time">
                    Updated: {{updatedAt}}
                </div>
            </div>
        </body>
        </html>
        """;

        return Results.Content(
            html,
            "text/html; charset=utf-8");
    });

// MCP Endpoint 還是一樣放 /mcp
app.MapMcp("/mcp");

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

這邊其實沒有做什麼複雜的東西

只要 Request 是進 /mcp,我就先看 Authorization Header

這次直接規定要帶 Bearer my-mcp-secret-key

Token 不對就直接回 401 Unauthorized

一般網站首頁不受影響,只有 MCP Endpoint 需要驗證

API Key 我這篇直接寫死,單純讓範例比較容易看懂

不過順帶一提,最近被駭客上了一課

現在這種 API Key、Token、Password 我自己是真的會勤勞一點,正式環境盡量放 Environment Variable 或 Secret Manager,不要直接塞 Source Code,更不要丟進 Git

這篇只是測試,所以先直接寫死就好

OpenCode 帶上 Bearer Token

Server 這邊開始檢查 Token 之後,OpenCode 當然也要一起帶過來

修改上一篇opencode.jsonc

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "horoscope": {
        "type": "remote",
        "url": "http://localhost:5100/mcp",
        "oauth": false,
        "headers": {
          "Authorization": "Bearer my-mcp-secret-key"
        },
        "protocol": "auto",
        "codemode": false
      }
    }
  }
}

跟上一篇比起來其實就只是多了 headers

OpenCode 連 MCP Server 的時候,就會一起送出 Authorization: Bearer my-mcp-secret-key

ASP.NET Core 收到 Request 之後先檢查 Token,正確才會繼續進 MCP

先故意給錯 Token

我自己測這種東西會先故意給錯 Token,看是不是真的有擋到

例如把 OpenCode 改成 Bearer wrong-key

再執行 opencode mcp list

這時 ASP.NET Core 應該直接回 401 Unauthorized

再把 Token 改回 my-mcp-secret-key

重新連線後就可以正常 Connected

這樣至少先確認不是只有 Code 看起來像有驗證,結果實際上根本沒擋到

再測一次 UpdateHoroscope

接著回 OpenCode,Prompt 跟上一篇差不多

今天是 2026-09-18

請幫我產生雙子座今天的星座運勢,
使用繁體中文,大約 100 字,
適合直接顯示在網站上

產生完成後,
請更新網站上的雙子座今日運勢

OpenCode 一樣先產生內容,再呼叫 UpdateHoroscope

差別只是現在每次連 MCP Server 都要先通過 Bearer Token 驗證

Token 正確才可以真的進到 MCP Tool

網站重新整理之後,一樣可以看到剛剛 OpenCode 產生的雙子座運勢

簡單結論一下

上一篇直接開 /mcp 在 localhost 測其實沒什麼問題

但 MCP Tool 一旦開始可以修改 Server Data,我覺得至少先加一層基本驗證比較合理

這次只是多一個 Bearer Token,知道 MCP URL 還不夠,還要有正確 Token 才能真的呼叫 Tool

當然這篇為了測試方便直接把 Token 寫死

真的上線就不要這樣搞,尤其我最近才被現實教育過一次,這種 Secret 還是乖乖放 Environment Variable 或 Secret Manager 比較安心

這篇先把最基本的 MCP Authentication 補起來

後面有機會再來測正式 OAuth,不然現在一次放太多東西進來又會開始亂掉,專案真實狀況加入OAuth 的驗證吧

如果不會寫,反正現在有 AI :)

--

The bug existed in all possible states. Until I ran the code.

如果這篇文章有幫助到您幫我分享一下,讓我有寫下去的動力...