ใน Blog นี้มาจด How To หลังจากที่ได้ฟังมาหลายๆที่ และลอง MCP ถ้าเป็น dotnet ถ้าเรามี Code เดิมอยู่แล้ว อยากมาปรับให้ตัวมันเองเป็น MCP Server ต้องทำอย่างไรบ้าง ลองตามมาอ่านกันครับ
Table of Contents
MCP คือ อะไร ?
MCP หรือ Model Context Protocol เป็นมาตรฐานกลางที่ช่วยให้ AI Model (LLM) มันมีความสามารถเพิ่มขึ้น เพราะข้อจำกัดของ Model มีองค์ความรู้จำกัด และข้อมูลบางอัน อาจจะมีการมโน (hallucination) ได้
ซึ่งก่อนที่จะมี MCP ถ้าเราอยากให้ LLM มันรู้จักกับ Tools/Service ที่เราทำไว้ไว้ ต้องไปเขียนวิธีการเชื่อมต่อตามที่ AI Model (LLM) แค่ละค่ายนั้นกำหนดขึ้นมาเอง หรือ แต่ละ Library ที่กำหนดไว้ ถ้าเรามีเปลี่ยน Model อย่างจาก GPT > Claude > Qwen มีกรี๊ดสิครับ
MCP เลยมาเป็นคำตอบ ทำมาตรฐานกลางเลย ให้ทุกคนเชื่อมได้ง่าย และ AI Model (LLM) เอาไป Implement ด้วยนั่นเองครับ โดยก่อนที่เราใช้ MCP ได้ อย่างแรกที่ต้องคิดถึงก่อน
เลือก Model ที่มีความสามารถ Function / Tools Calling
หลังจากที่ AI Model (LLM) มันรู้จัก Tools มันจะช่วยแก้ปัญหา องค์ความรู้จำกัด และลดการมโนได้ อาทิ เช่น
🔴 เรื่องของเวลา ปกติ Model มันไม่มีนาฟิกาในตัวนะ มันรู้แค่วันจากข้อมูลที่ Training มา ถ้าเรามี Tools ด้านเวลาเข้ามาก็จะช่วย AI มันถาม และตอบได้
🔴 เรื่องข้อมูลที่เป็น Local ของเราเอง บางที AI มันไม่รู้จักหรอกว่า Order 14334 คือ อะไร
- ถ้าแบบเดิมๆ AI มันจะมโน หรือ ตอบว่าไม่ทราบ ถ้า Prompt ดี
- ถ้าแบบใหม่ AI มันจะเรียก Function ที่เราเตรียมไว้ให้ดึงข้อมูลของ Order 14334
🔴 เรื่องของการคำนวณ AI ถ้ามีสูตร AI อาจจะคิดผิดได้ แต่ถ้าเรามี Tools ที่เตรียมไว้ AI มาเรียกใช้ มันคิดตามสูตรที่เรากำหนดไว้
อย่าลืม Validate ข้อมูลด้วยนะ ก่อนเข้า Tools ด้วยนะ
🔴 เรื่องการ Actions กับ Environment รอบข้าง - ทำให้ AI มีแขนขาไปสั่งการ เช่น การจองตั๋ว / แสดงข้อมูลขึ้นหน้าจอ หรือ สั่งให้มอเตอร์หมุน เป็นต้น
การทำงานของ MCP Server
- Transport (รูปแบบการเชื่อมต่อ)
🔴 ก่อนที่จะลงมือ Coding กัน ต่องมาเข้าใจรูปแบบการเชื่อมต่อกับ MCP Server กันก่อน เพราะมันมีผลการเอา AI+APP มาเชื่อมเหมือนกันนะ
🔴 ดังนั้นต้องมาดูว่ารูปแบบการเชื่อมต่อ stdio / StreamableHTTP / SSE (deprecated) มันต่างกันยังไง
| Transport Type | stdio | StreamableHTTP | SSE (deprecated) |
|---|---|---|---|
| Desciption | สื่อสาร 2 ทางระหว่าง Process อ่านพวก stdin/stdout จาก Pipe ถ้าสนใจ Pipe มี Blog นะ | HTTP response streaming / chunked | สื่อสารจาก Service > Client HTTP response แบบ text/event-stream ถ้าเปิด Connection แล้วคุยได้ปกติ |
| Support Data Type | text / binary | text | text / binary |
| Use Case | CLI/Local พวก Claude Desktop / Gemini CLI | Web/API | Web/API (Legacy) เก่าแล้ว |
| Latency | Lowest | Medium | High |
| Remote Access | No | Yes | Yes |
| Multi-Client | No | Yes | Yes |
หลักๆตอนนี้เหลือ 2 Choice ให้เลือก stdio(local) / StreamableHTTP (WebAPI)
- Context type
🔴 สิ่งที่ MCP Server ตอบกลับเรามา ตอนนี้มี 3 รูปแบบ
| Context Type | Description | Security Concern | Example |
|---|---|---|---|
| Prompts (User-controlled) | - Interactive templates invoked by user choice - มองว่าเป็น Prompt ที่ MCP นั้นแนะนำให้เราใช้งาน ตามแต่ละ Scenario | - validate prompt inputs /outputs prevent injection attacks - unauthorized access to resources. | Slash commands, menu options |
| Resources (Application-controlled) | - Contextual data attached and managed by the client - ให้ข้อมูล เช่น สูตร หรือ ตัวอย่าง - อาจจะรวมถึงพวกอารมณ์แบบ Cache เช่น ข้อมูลกองทุน บราๆ | - Data must be encoded - Validate Trust URI - Access Control | File contents, git history |
| Tools (Model-controlled) | - Functions exposed to the LLM to take actions - ปกติทำอันนี้เยอะสุด ช่วยไม่ให้ AI Hallucination | - Validate + Sanitize Inputs - access controls - Rate Limit | API POST requests, file writing |
ลองมาปรับ Code .NET เดิมเป็น MCP Server
🔴 ก่อนจะเริ่มใช้งาน เราต้องมาตั้ง Project จำลองกันก่อนครับ อันนี้ ผมจะเตรียม InvestmentAPI และกันครับ ตอนแรก dotnet9 เพิ่งขยับเป็น dotnet10 สดๆร้อนๆ โดย WebAPI ของผม Endpoint ไว้ประมาณนี้ครับ

- CalcInvReturnController (คำนวณผลตอบแทนการลงทุน)
- GET /api/calcinvreturn/simple-return
- GET /api/calcinvreturn/cagr คำนวณ CAGR (Compound Annual Growth Rate) - ExchangeRateController (อัตราแลกเปลี่ยน)
- GET /api/exchangerate/specificrate
- GET /api/exchangerate/max-date
- GET /api/exchangerate/all-pairs-max-dates
- POST /api/exchangerate/add (Body JSON)
- PUT /api/exchangerate/update/{id:guid} (Body JSON)
- DELETE /api/exchangerate/delete/{id:guid}
🔴 Install NuGet Package ที่ต้องการครับ ModelContextProtocol (App) / ModelContextProtocol.AspNetCore (WebAPI)
# stdio(local) dotnet add package ModelContextProtocol --prerelease # Or StreamableHTTP (WebAPI) / SSE dotnet add package ModelContextProtocol.AspNetCore --prerelease
🔴 ที่ Program.cs เพิ่ม Code ลงไป เพื่อให้บอกให้มันรู้ว่าต้องทำหน้าที่เป็น MCP Server นะ
- แบบ STDIO โดยมีรายละเอียด ดังนี้
- AddMcpServer - เพิ่มความสามารถให้เป็น MCPServer
- WithStdioServerTransport - โดยใช้รูปแบบ STDIO (Standard IO)
- WithPromptsFromAssembly / WithResourcesFromAssembly / WithToolsFromAssembly ให้ไปกวาด Class + Method ที่ใส่ Tag ที่กำหนดไว้มา register เป็น Prompts, Resources หรือ Tools
public class Program
{
private static void Main(string[] args)
{
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
...
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithPromptsFromAssembly()
.WithResourcesFromAssembly()
.WithToolsFromAssembly();
// Register services
builder.Services.AddHttpClient<ExchangeRateService>();
// ....
}- แบบ Streamable HTTP/ SSE
- AddMcpServer - เพิ่มความสามารถให้เป็น MCPServer
- WithHttpTransport- โดยใช้รูปแบบ Streamable HTTP / SSE default จะ expose ตามนี้
🏍️ Streamable HTTP - basepath:port
🛵 SSE - basepath:port/sse
- WithPromptsFromAssembly / WithResourcesFromAssembly / WithToolsFromAssembly ให้ไปกวาด Class + Method ที่ใส่ Tag ที่กำหนดไว้มา register เป็น Prompts, Resources หรือ Tools
- นอกจากนี้แล้ว ยังต้องเพิ่ม app.MapMcp(); เพิ่มให้มัน Expose Endpoint ส่วน MCP ออกไป
public class Program
{
private static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddOpenApi();
builder.Services.AddControllers();
builder.AddObservability();
// Add PostgreSQL DbContext
builder.Services.AddDbContext<InvestmentDbContext>(
options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))
);
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithPromptsFromAssembly()
.WithResourcesFromAssembly()
.WithToolsFromAssembly();
// Register services
builder.Services.AddHttpClient<ExchangeRateService>();
builder.Services.AddScoped<ExchangeRateService>();
builder.Services.AddScoped<CalcInvReturnService>();
var app = builder.Build();
// Automatically create or migrate the database
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<InvestmentDbContext>();
// Use EnsureCreated or Migrate
dbContext.Database.Migrate(); // Applies migrations
// dbContext.Database.EnsureCreated(); // Creates the database without migrations
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.MapMcp();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}🔴 หลังจากเราบอกมาปรับ Code ในส่วนของ API เดิมเรา ที่จะเปิดเป็น Tools / Resource / Prompts
- Tools
- เติม [McpServerToolType] ที่หัวของ Class เพื่อบอกว่า เราจะ Expose ตัวนี้เป็น Tools ใน Builder พอเราเรียกตัว .WithResourcesFromAssembly() มันจะไปกวาดมาให้
- ที่ Method ของเราเติม [McpServerTool] และเขียนคำอธิบายลงไป ถ้ายิ่งละเอียด ตัว LLM จะเรียกใช้ได้ง่าย
- Name ส่วนที่ LLM ตีความ
- Title ส่วนคำอธิบาย และเป็นส่วนที่คนเรานั้นเข้าใจได้
[McpServerTool(
Name = "calc_cagr",
Title = "Calculate Compound Annual Growth Rate (CAGR)"
)]
[HttpGet("cagr")]
public IActionResult CAGR(double initialValue, double finalValue, int years)
{
try
{
//Service + Validate Logic
double result = _calcService.CalculateCAGR(initialValue, finalValue, years);
return Ok(new { CAGR = result });
}
catch (ArgumentException ex)
{
return BadRequest(ex.Message);
}
}- Resource
- เติม
[McpServerResourceType]ที่หัวของ Class เพื่อบอกว่า เราจะ Expose ตัวนี้เป็น Resource ใน Builder พอเราเรียกตัว .WithResourcesFromAssembly() มันจะไปดึงมา register ให้ - ที่ Method ของเราเติม [McpServerResource] และเขียนคำอธิบายลงไป ถ้ายิ่งละเอียด ตัว LLM จะเรียกใช้ได้ง่าย
- Name ส่วนที่ LLM เข้าใจ
- UriTemplate ส่วน URL ที่ LLM จะมาร้องของข้อมูล
- MimeType รูปแบบของข้อมูลที่ส่งกลับ เช่น Text หรือ Json
[McpServerResource(
UriTemplate = "api://calcinvreturn/formulas/{type}",
Name = "investment_calculation_formula",
MimeType = "text/plain"
)]
[Description("Returns the formula for simple return or CAGR as plain text")]
[HttpGet("formulas/{type}")]
public string GetCalculationFormula(string type)
{
if (type.ToLower() == "simple-return")
{
return "Simple Return Formula: (Final Value - Initial Value) / Initial Value";
}
else if (type.ToLower() == "cagr")
{
return "CAGR Formula: (Final Value / Initial Value)^(1 / Years) - 1";
}
else
{
return "Invalid type. Available: simple-return, cagr";
}
}
[McpServerResource(
UriTemplate = "api://calcinvreturn/examples/{type}",
Name = "investment_calculation_example",
MimeType = "application/json"
)]
[Description("Returns an example calculation for simple return or CAGR as JSON")]
[HttpGet("examples/{type}")]
public string GetCalculationExample(string type)
{
if (type.ToLower() == "simple-return")
{
var example = new
{
Type = "SimpleReturn",
InitialValue = 1000,
FinalValue = 1200,
Calculation = "(1200 - 1000) / 1000 = 0.2",
Result = 0.2
};
return JsonSerializer.Serialize(example);
}
else if (type.ToLower() == "cagr")
{
var example = new
{
Type = "CAGR",
InitialValue = 1000,
FinalValue = 1464.1,
Years = 5,
Calculation = "(1464.1 / 1000)^(1 / 5) - 1 ≈ 0.08",
Result = 0.08
};
return JsonSerializer.Serialize(example);
}
else
{
return JsonSerializer.Serialize(
new { Error = "Invalid type. Available: simple-return, cagr" }
);
}
}สำหรับอันนี้ ผมให้มันให้ข้อมูลสูตร และตัวอย่างการคำนวณไปแทนครับ จะได้ไม่เหมือน Tools มากไป จริงมันเขียนได้เหมือนกับ Tools นะ ชึ้นกับเรา Coding เลย
- Prompt
- เติม
[McpServerPromptType]ที่หัวของ Class เพื่อบอกว่า เราจะ Expose ตัวนี้เป็น Prompt ใน Builder พอเราเรียกตัว.WithResourcesFromAssembly()มันจะไปกวาดมาให้ - ที่ Method ของเราเติม [McpServerPrompt] และเขียนคำอธิบายลงไป
สำหรับเคสนี้ เรียกว่าเราเตรียม Prompt ให้ AI เข้าไปใช้งานต่อ เพื่อสร้างคำถามที่ชัดเจนมากขึ้น และเรียก Tools เพื่อคำนวณจริงได้ หรือ เอามาช่วยหลังได้ผลลัพธ์ ก็ได้ GenerateInvestmentAdvicePrompt - แนะนำการลงทุน หรืออย่าง RiskAssessmentPrompt - จัดการความเสี่ยง เป็นต้น
[McpServerPrompt(Name = "investment_advice_prompt"),Description("Generates investment advice based on CAGR")]
public string GenerateInvestmentAdvicePrompt(double cagr, int years)
{
return $"Based on a CAGR of {cagr:P2} over {years} years, consider diversifying your portfolio and consulting a financial advisor for long-term growth.";
}
[McpServerPrompt(Name = "risk_assessment_prompt"),Description("Provides a risk assessment prompt for investments")
]
public string RiskAssessmentPrompt(string investmentType)
{
return $"Assess the risk for {investmentType} investments: Consider market volatility, historical returns, and your risk tolerance before investing.";
}💻 Code เต็มๆ + ภาพรวมหลังปรับ 💻

ตอนนี้เราจะได้ภาพมาประมาณนี้แล้ว REST API แบบเดิมๆ กลายร่างเป็น MCP Server Code ที่ปรับก็ประมาณนี้ครับ
- Controller ที่ปรับให้เป็น MCP Server CalcInvReturnController.cs / ExchangeRateController.cs
- Program.cs ที่ปรับให้ Expose Endpoints สำหรับ MCP Server
ต่อไปเป็นทดสอบ จากตัว Inspector และ Claude Desktop เดียว Blog หน้าค่อยปรับเป็น Agent อิอิ
ทดสอบ MCP Server ที่ปรับแล้ว
อันนี้เราใช้ Tools อย่างตัว Inspector ที่มันจะบอกได้ว่า API ของเรามีอะไรบ้าง
🔴 Install modelcontextprotocol/inspector (Required Node.js: ^22.7.5)
npx @modelcontextprotocol/inspector


🔴 จากนั้นเข้า http://localhost:6274 เราพบเครื่องมือที่ให้เราช่วยตรวจ API ของเราได้เลยครับ ตรงนี้พบว่ามี MCP ทั้ง 3 แบบ ทั้ง stdio / StreamableHTTP / SSE - ตอน Test อารมณ์แบบ Postman / RESTClient การคุม Error ขึ้นกับเราเขียน Code



- STDIO
- ตอนรันมีท่ายากกว่าชาวบ้านนิดนึง ลองไปแกะจาก Code ตัวอย่างไร
npx @modelcontextprotocol/inspector dotnet run --project PATH/TO/YOUR_MCP.csproj -- PARAM_IF_NEED npx @modelcontextprotocol/inspector dotnet run --project D:/InvestmentAPI/InvestmentAPI.csproj -- --stdio
- อาจจะมีเคส Error ต้องระวังเขียน Log ใน Code ด้วย
Error from MCP server: SyntaxError: Unexpected token 'N', " Name: Syst"... is not valid JSON
at JSON.parse (<anonymous>)
at deserializeMessage (file:///C:/Users/Chatr/AppData/Local/npm-cache/_npx/5a9d879542beca3a/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js:26:44)
at ReadBuffer.readMessage (file:///C:/Users/Chatr/AppData/Local/npm-cache/_npx/5a9d879542beca3a/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js:19:16)
at StdioClientTransport.processReadBuffer (file:///C:/Users/Chatr/AppData/Local/npm-cache/_npx/5a9d879542beca3a/node_modules/@modelcontextprotocol/sdk/dist/esm/client/stdio.js:141:50) ...- ที่ Tools เลือก STDIO หลังจากกด Connect เข้าไปดู Tools / Resources / Prompt ได้

- SSE

- เลือก SSE และกำหนด Path [YOUR_IP]:[PORT]/sse
- หลังจากกด Connect เข้าไปดู Tools / Resources / Prompt ได้
- Streamable HTTP (อันนี้ภาพเยอะหน่อย)
- เลือก Streamable HTTP และกำหนด Path [YOUR_IP]:[PORT]
- หลังจากกด Connect เข้าไปดู Tools ในรูปมีทั้งเคสที่ Pass / Fail
- Tools ต่อ ถ้าลองพวกแก้ข้อมูล Add / Edit / Delete ก็ขึ้นนะ : Add with Form / JSON



- Tools ส่วน Delete

- Resources / Prompt ได้


Source Code เต็มๆ https://github.com/pingkunga/semantic-kernel-financial-plugin-sample/tree/feature/investapi
ลองเอาไปใช้กับ Claude Deskop
📌 ก่อนใช้งานอย่าลืมตรวจสอบ MCP Tools Name ห้ามมี Space ไม่งั้นจะมี Error

"tools.XXX.FrontendRemoteMcpToolDefinition.name: String should match pattern '^[a-zA-Z0-9_]{1,64}$'"📌 ทางแก้เอา Space ออก ตามนี้เลย

📌 การใช้งานมี 2 ส่วน
- ฝั่ง mcp server
dotnet watch runหรือ จะ start เป็น container ก็ได้ (ใน Code มีแบบ stdio ให้ลองด้วยนะ) - ฝั่ง claude ต้องแก้ claude_desktop_config.json ใน path ที่ติดตั้ง Claude เพิ่ม mcp server ของเราเข้าไป อย่างของผม run เป็น http://localhost:5205/ ส่วน "investment-api" จะเป็นตามนี้
{
"mcpServers": {
"MSSQL MCP": {
"command": "D:\\NET_LAB\\SQL-AI-samples\\MssqlMcp\\dotnet\\MssqlMcp\\bin\\Debug\\net8.0\\MssqlMcp.exe",
"env": {
"CONNECTION_STRING": "Server=192.168.1.4,14330;Database=POSDB;User Id=Sample;Password=Sample;TrustServerCertificate=True"
}
},
"investment-api": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:5205/"]
}
}
}📌 จากนั้นเข้า Claude มาตรวจสอบความพร้อม


📌 ลองถามได้เลยครับ มันจะไป Call MCP Server เรา และตรวจ JSON หรือ Log ขา Server มันแสดงการ Call ไหม

Next Step
หลังจากเพิ่มใน Claude Desktop และ เดี๋ยว Blog ตอนต่อไป ลองใช้งานกับ Agent ถ้าดูจาก Code Project หลักยังใช้งาน Semantic Kernel ครับ 555 ว่าจะเปลี่ยนไปใช้ Microsoft Agent Framework และต้องปรับเพิ่มเรื่อง Security ด้วย ตอนนี้ไม่มี API Key เดี๋ยวเป็นข่าวดัง ฮ่าๆ
Resource
- https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples
- https://modelcontextprotocol.io/docs/getting-started/intro
- Source Code เต็มๆ https://github.com/pingkunga/semantic-kernel-financial-plugin-sample/tree/feature/investapi
Discover more from naiwaen@DebuggingSoft
Subscribe to get the latest posts sent to your email.






