Yapılandırılmış çıktı
Makine çevirisi
Bu sayfa İngilizce dokümantasyondan otomatik olarak çevrildi; esas alınması gereken sürüm İngilizce sayfadır. Yanlış görünen bir şey varsa, nasıl bildireceğinizi Çeviriler sayfası açıklar.
Düz bir str döndüren bir araç, sonucu iki kez üretir: content içinde metin olarak ve structured_content içinde {"result": "..."} olarak.
Bu sayfa o ikinci kanalla ilgili: nereden geldiği, alabileceği her biçim ve SDK'nın onu nasıl tutarlı tuttuğu.
Kısaca: dönüş türü açıklaması (annotation) çıktı şemasıdır. Onu zaten yazdınız.
Çıktı şeması
from mcp.server import MCPServer
mcp = MCPServer("Weather")
READINGS = {"London": 17, "Cairo": 34, "Reykjavik": 4}
@mcp.tool()
def get_temperature(city: str) -> int:
"""Current temperature in a city, in whole degrees Celsius."""
return READINGS[city]
Önemli olan satır imza: -> int.
Bu sayede SDK'nın tools/list sırasında gönderdiği araç, parametrelerinizden oluşturduğu girdi şemasının yanında (onu Araçlar sayfası anlatır) bir de output_schema taşır:
{
"properties": {
"result": {"title": "Result", "type": "integer"}
},
"required": ["result"],
"title": "get_temperatureOutput",
"type": "object"
}
Tek başına bir int JSON nesnesi değildir, bu yüzden SDK onu {"result": ...} içine sarar. Aracı çağırdığınızda iki kanal da dolar:
result.content # [TextContent(text="17")]
result.structured_content # {"result": 17}
Her skaler aynı sarmalayıcıyı alır: str, int, float, bool, bytes, None.
İki kanal
Neden aynı değer iki kez gönderiliyor?
contentmodel içindir. Bir dil modeli metin okur; sonucun gördüğü tek kısmı budur.structured_content, modelin içinde çalıştığı uygulama içindir: "17" geçen bir cümle değil,17isteyen kod.output_schemaikisi arasındaki sözleşmedir ve araç daha hiç çağrılmadan yayımlanır.
Siz tek bir Python değeri döndürürsünüz. Üçünü de SDK doldurur.
Bir model döndürme
Biçimi bir Pydantic BaseModel olarak bildirin ve bir örneğini döndürün:
from pydantic import BaseModel, Field
from mcp.server import MCPServer
mcp = MCPServer("Weather")
class WeatherData(BaseModel):
temperature: float = Field(description="Degrees Celsius.")
humidity: float = Field(description="Relative humidity, 0 to 1.")
conditions: str
@mcp.tool()
def get_weather(city: str) -> WeatherData:
"""Current weather for a city."""
return WeatherData(temperature=16.2, humidity=0.83, conditions="Overcast")
Artık şema WeatherData'nın kendisi. Sarmalayıcı yok, result anahtarı yok:
{
"properties": {
"temperature": {"description": "Degrees Celsius.", "title": "Temperature", "type": "number"},
"humidity": {"description": "Relative humidity, 0 to 1.", "title": "Humidity", "type": "number"},
"conditions": {"title": "Conditions", "type": "string"}
},
"required": ["temperature", "humidity", "conditions"],
"title": "WeatherData",
"type": "object"
}
structured_content alan alan o nesnedir:
result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions": "Overcast"}
Model de dışarıda kalmaz. SDK aynı nesneyi content için JSON metnine serileştirir:
{
"temperature": 16.2,
"humidity": 0.83,
"conditions": "Overcast"
}
temperature ve humidity üzerindeki Field(description=...) bilgisinin şemaya düştüğüne dikkat edin. Girdilerinizi tanımlayan aynı Field, çıktılarınızı da tanımlar.
Info
FastAPI'nin response_model'ını kullandıysanız bunu zaten biliyorsunuz: bildirilen yanıt olarak
bir Pydantic modeli, sizin yerinize serileştirilir ve belgelenir. Tek fark, burada bildirimin
tamamının dönüş açıklaması olmasıdır.
Bir TypedDict
Her biçim bir sınıfı hak etmez. Bir TypedDict aynı şemayı üretir:
from typing import TypedDict
from mcp.server import MCPServer
mcp = MCPServer("Weather")
class WeatherData(TypedDict):
temperature: float
humidity: float
conditions: str
@mcp.tool()
def get_weather(city: str) -> WeatherData:
"""Current weather for a city."""
return WeatherData(temperature=16.2, humidity=0.83, conditions="Overcast")
TypedDict çalışma zamanında düz bir dict'tir; siz de onu oluşturup döndürürsünüz. Şema, doğrulama ve structured_content, BaseModel sürümüyle birebir aynıdır (TypedDict'te yeri olmayan açıklamalar hariç).
Bir dataclass
Dataclass'lar da çalışır; öznitelikleri tür ipucu taşıyan herhangi bir sıradan sınıf da öyle. SDK arka planda açıklamalardan bir Pydantic modeli oluşturur.
from dataclasses import dataclass
from mcp.server import MCPServer
mcp = MCPServer("Weather")
@dataclass
class WeatherData:
temperature: float
humidity: float
conditions: str
@mcp.tool()
def get_weather(city: str) -> WeatherData:
"""Current weather for a city."""
return WeatherData(temperature=16.2, humidity=0.83, conditions="Overcast")
Üç yazım, tek şema. Kod tabanınızda hangisi varsa onu kullanın.
Listeler
Bir list[...] de JSON nesnesi değildir, bu yüzden {"result": ...} sarmalayıcısını alır; öğe türünüz içinde bir $defs başvurusu olarak yer alır:
from pydantic import BaseModel
from mcp.server import MCPServer
mcp = MCPServer("Weather")
class WeatherData(BaseModel):
temperature: float
humidity: float
conditions: str
@mcp.tool()
def get_forecast(city: str, days: int) -> list[WeatherData]:
"""Daily forecast for a city."""
return [WeatherData(temperature=16.2 + day, humidity=0.83, conditions="Overcast") for day in range(days)]
{
"$defs": {
"WeatherData": {
"properties": {
"temperature": {"title": "Temperature", "type": "number"},
"humidity": {"title": "Humidity", "type": "number"},
"conditions": {"title": "Conditions", "type": "string"}
},
"required": ["temperature", "humidity", "conditions"],
"title": "WeatherData",
"type": "object"
}
},
"properties": {
"result": {"items": {"$ref": "#/$defs/WeatherData"}, "title": "Result", "type": "array"}
},
"required": ["result"],
"title": "get_forecastOutput",
"type": "object"
}
İki günlük bir tahmin istediğinizde structured_content, {"result": [{...}, {...}]} olur. content ise öğe başına bir tane olmak üzere iki TextContent bloğuna dönüşür: liste, model için tek bir dizge olarak dökülmek yerine düzleştirilir.
tuple[...], union'lar ve Optional[...] aynı şekilde sarılır.
Sözlükler
dict[str, ...] zaten bir JSON nesnesi olan tek generic türdür, bu yüzden sarılmaz:
from mcp.server import MCPServer
mcp = MCPServer("Weather")
READINGS = {"London": 16.2, "Cairo": 34.1, "Reykjavik": 4.4}
@mcp.tool()
def get_temperatures(cities: list[str]) -> dict[str, float]:
"""Current temperature for each city, in degrees Celsius."""
return {city: READINGS[city] for city in cities}
{
"additionalProperties": {"type": "number"},
"title": "get_temperaturesDictOutput",
"type": "object"
}
result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
Anahtarlar str olmalıdır. Bir dict[int, float] JSON nesnesi olamaz, bu yüzden {"result": ...} sarmalayıcısına geri düşer.
Doğrulama
output_schema belgeleme değildir. Fonksiyonunuz ne döndürürse döndürsün, sunucudan çıkmadan önce ona göre doğrulanır.
Değeri elle oluşturduğunuz sürece bunu fark etmezsiniz: Pydantic, WeatherData'nızın bir WeatherData olduğundan zaten emin olmuştur. Bunu, verinin sizin denetlemediğiniz bir yerden geldiği gün fark edersiniz:
import json
from pydantic import BaseModel
from mcp.server import MCPServer
mcp = MCPServer("Weather")
UPSTREAM = {"London": '{"temperature": 16.2, "conditions": "Overcast"}'}
class WeatherData(BaseModel):
temperature: float
humidity: float
conditions: str
@mcp.tool()
def get_weather(city: str) -> WeatherData:
"""Current weather for a city."""
return json.loads(UPSTREAM[city])
Açıklama WeatherData vaat ediyor. Üst servisin yanıtı humidity göndermeyi bırakmış.
Check
get_weather'ı çağırdığınızda istemciye sessizce yarı boş bir nesne vermez. Çağrı başarısız
olur ve hatanın ilk satırları alanın adını verir:
Error executing tool get_weather: 1 validation error for WeatherData
humidity
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
Bu metin, is_error=True ile araç sonucu olarak geri döner; böylece model, var olmayan bir hava
durumunu kendinden emin biçimde okumak yerine çağrının başarısız olduğunu bilir.
Bu arada, -> WeatherData bir araçtan düz bir dict döndürmek sorun değil. json.loads'un ürettiği tam olarak buydu. Doğrulama Python türüne değil, değere uygulanır.
Devre dışı bırakma
Bazen dönüş açıklaması protokol için değil, tür denetleyiciniz içindir. structured_output=False geçirin; araç yalnızca metin üretir:
from mcp.server import MCPServer
mcp = MCPServer("Weather")
@mcp.tool(structured_output=False)
def weather_report(city: str) -> str:
"""A human-readable weather report for a city."""
return f"{city}: 17 degrees, overcast, light rain easing by evening."
output_schema yok, sarmalama yok, doğrulama yok. structured_content None'dır ve content döndürdüğünüz dizgedir.
Tersi olan structured_output=True, otomatik algılamayı bir zorunluluğa çevirir: dönüş türü şema üretemeyen bir araç, metne geri düşmek yerine içe aktarma anında istisna fırlatır.
Tür ipucu olmayan bir sınıf
İstemeden yapılandırılmamış sonuca varmanın bir yolu vardır: gövdesinde hiç açıklama olmayan bir sınıf döndürmek.
from mcp.server import MCPServer
mcp = MCPServer("Weather")
class Station:
def __init__(self, name: str, online: bool):
self.name = name
self.online = online
@mcp.tool()
def get_station(name: str) -> Station:
"""Look up a weather station by name."""
return Station(name=name, online=True)
Station, name ve online değerlerini __init__ içinde atar, ama sınıf hiçbir şey bildirmez. SDK sınıf açıklamalarını okur, hiçbir şey bulamaz ve vazgeçer.
Warning
Sessizce vazgeçer. output_schema None'dır, structured_content None'dır ve modelin
okuduğu metin nesnenin repr'idir:
"<server.Station object at 0x7f539d75b230>"
Hata yok, uyarı yok, işe yaramaz bir araç. Açıklamaları sınıf gövdesine taşıyın ya da
structured_output=True geçirin; bu, modül içe aktarıldığı anda durumu kesin bir hataya çevirir:
Function get_station: return type <class 'server.Station'> is not serializable for structured output.
Tip
Tam denetim mi gerekiyor (CallToolResult'ı kendiniz oluşturmak ya da uygulamanın görüp
modelin göremediği bir _meta eklemek)? Bunun yeri Düşük seviyeli Server.
Özet
- Dönüş türü açıklaması çıktı şemasıdır.
tools/listiçindeoutput_schemaolarak yayımlanır. - Skalerler, listeler, tuple'lar ve union'lar
{"result": ...}içine sarılır. Modeller,TypedDict'ler, dataclass'lar, açıklamalı sınıflar vedict[str, ...]zaten nesnedir ve oldukları gibi kalırlar. - Her sonuç hem
content(metin, model için) hem destructured_content(veri, uygulama için) taşır. - Döndürdüğünüz şey şemaya göre doğrulanır. Uyuşmazlık bozuk bir sonuç değil, bir araç hatasıdır.
structured_output=Falsebir aracı devre dışı bırakır. Tür ipucu olmayan bir sınıf sessizce devre dışı kalır; buna dikkat edin.
Artık bir aracın geri söyleyebileceği her şeye hâkimsiniz. Sırada ikinci ilkel yapı var: Kaynaklar.