MCP
MCP (Model Context Protocol) is the spec language models use to call tools.
A NestRS MCP server is a struct with a #[mcp] decorator: DI handles its
dependencies, #[tool] defines each tool, the server self-mounts on the
HTTP transport at the path you choose.
Below is a complete MCP server, served on http://localhost:3000 — the full
version lives in apps/mcp.
A working MCP server
Section titled “A working MCP server”use std::sync::Arc;
use nestrs_mcp::mcp;use nestrs_mcp::{ tool, tool_handler, tool_router, CallToolResult, Content, McpError, Parameters, ServerHandler,};use validator::Validate;
use crate::weather::dto::CoordsParams;use crate::weather::service::WeatherProvider;
#[mcp(path = "/mcp")]#[derive(Clone)]pub struct WeatherTool { #[inject] weather: Arc<dyn WeatherProvider>,}
#[tool_router]impl WeatherTool { #[tool(description = "Return the current weather at the given GPS coordinates (Open-Meteo).")] async fn current_weather( &self, Parameters(params): Parameters<CoordsParams>, ) -> Result<CallToolResult, McpError> { params .validate() .map_err(|e| McpError::invalid_params(e.to_string(), None))?;
let report = self .weather .current(params.latitude, params.longitude) .await .map_err(internal)?;
let summary = format!( "{:.1}°C, wind {:.1} km/h @ {:.0}° (code {}, observed {})", report.temperature_c, report.wind_speed_kmh, report.wind_direction_deg, report.weather_code, report.observed_at, );
Ok(CallToolResult::success(vec![Content::text(summary)])) }}
#[tool_handler]impl ServerHandler for WeatherTool {}use serde::{Deserialize, Serialize};use validator::Validate;
#[derive(Clone, Debug, Deserialize, Serialize, schemars::JsonSchema, Validate)]pub struct CoordsParams { #[validate(range(min = -90.0, max = 90.0))] pub latitude: f64, #[validate(range(min = -180.0, max = 180.0))] pub longitude: f64,}#[mcp(path = "/mcp")]mounts the server on the HTTP transport at/mcp(Streamable-HTTP, the standard MCP transport).#[inject] weather: Arc<dyn WeatherProvider>— the tool injects a trait object; the impl lives module-private withpub traitexposed.#[tool(description = ...)]registers the method as a tool the LLM client can call. Thedescriptionis what the model sees.Parameters<CoordsParams>— the framework deserializes the JSON arguments to the typed input.JsonSchemaderives the schema sent to the client;validatorrejects out-of-range values.
Run it
Section titled “Run it”$ just dev mcp2026-06-03T10:34:07.882Z INFO nestrs::http: bound 1 MCP server on 0.0.0.0:3000 POST /mcp → WeatherTool (1 tool)Point an MCP-aware client at http://localhost:3000/mcp:
{ "mcpServers": { "weather": { "url": "http://localhost:3000/mcp" } }}In your client, the tool current_weather becomes callable with
{ "latitude": 48.85, "longitude": 2.35 }:
14.2°C, wind 7.3 km/h @ 220° (code 3, observed 2026-06-03T10:34:00)Going further
Section titled “Going further”Multiple tools on one server
Section titled “Multiple tools on one server”Add more methods under #[tool_router] impl — each #[tool(...)] becomes
a separately callable tool. Share state through &self.
Authorization
Section titled “Authorization”McpAbilityBridge (provided by nestrs-authz with the mcp feature
flag) gates tool calls against an Ability. Same policy as HTTP/GraphQL.
Trait-based DI
Section titled “Trait-based DI”Injecting Arc<dyn WeatherProvider> keeps the concrete provider
module-private — a test app can swap in a stub by overriding the
provider via AppBuilder::override_dyn.
Reference
Section titled “Reference”apps/mcp/— the full example.crates/nestrs-mcp/—#[mcp],#[tool_router],#[tool],#[tool_handler].crates/nestrs-authz/(withmcpfeature) — the MCP authz bridge.