Vuoden puolivälissä 2026, MCP (Model Context Protocol) on käytännön standardi LLM-mallien yhdistämiseen työkaluihin. Anthropic esitteli sen, ja OpenAI, Google sekä laajempi ekosysteemi ovat ottaneet sen käyttöön. Cursor, Claude Desktop, ChatGPT ja mukautetut agentit puhuvat kaikki MCP:tä.
MCP-palvelin on tapa antaa LLM-agenteille pääsy palveluusi. Kun olet rakentanut yhden tai kaksi, huomaat protokollan olevan pieni. Kiinnostava suunnittelutyö on sen ympärillä: skeemat, virheenkäsittely, tunnistautuminen, suoratoisto, suorituskyky ja havainnoitavuus.
Tämä artikkeli perehtyy tuotantotasoisten MCP-palvelinten rakentamiseen TypeScriptillä. Keskitymme todellisessa agenttikäytössä kestäviin malleihin, emme vain protokollan mekaniikkaan.
MCP lyhyesti
MCP on asiakas-palvelinprotokolla, jossa:
- Palvelimet tarjoavat työkaluja, resursseja ja kehotteita.
- Asiakkaat ovat tavallisesti niitä käyttäviä LLM-agentteja.
Protokolla käyttää JSON-RPC 2.0:aa. Virallinen määrittely on lyhyt ja kannattaa lukea kerran. Siirtotapoina ovat stdio paikallisille prosesseille ja Streamable HTTP etäpalvelimille. Vanhempi HTTP+SSE-siirtotapa poistettiin käytöstä määrittelyn 2025-03-26 versiossa, joten siihen perustuvia oppaita tulee pitää historiallisina. Tunnistautuminen ja tietoturva kuuluvat protokollaan; tärkeimmät toteutukset tukevat OAuthia, API-avaimia ja vastaavia tapoja.
Palvelimen tehtävä on tarjota LLM-malleille hyödyllisiä kyvykkyyksiä löydettävässä ja käytettävässä muodossa.
Perusrakenne
Virallisen @modelcontextprotocol/sdk-paketin korkean tason McpServer-rajapinnalla vähimmäispalvelin näyttää tältä:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
server.registerTool(
"echo",
{
description: "Echo back the provided text.",
inputSchema: { text: z.string() },
},
async ({ text }) => ({
content: [{ type: "text", text }],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
(Sama SDK tarjoaa myös alemman tason Server-luokan ja setRequestHandler-käsittelijän ListToolsRequestSchema- ja CallToolRequestSchema-pyynnöille, jos haluat hallita käsittelijöitä kokonaan. Useimmille palvelimille McpServer.registerTool on lyhyempi ja vaikeampi toteuttaa väärin.)
Tämä on runko. Varsinainen työ on työkalukäsittelijöiden sisällössä ja toteutustavassa.
Malli 1: työkalusuunnittelun periaatteet
Ensimmäinen päätös koskee tarjottavia työkaluja ja niiden tarkkuustasoa.
Tavallinen virhe on tarjota taustalla oleva API sellaisenaan työkaluina. Jos käytössä on 200 REST-päätepistettä, 200 työkalun tarjoaminen on katastrofi. Mallit suoriutuvat heikommin liian monella työkalulla, kuvauksista tulee hallitsemattomia ja protokolla muuttuu sokkeloksi.
Suunnittele työkalut agenttien käyttötavan mukaan. Kukin tekee yhden tarkasti määritetyn asian, vastaanottaa selkeät syötteet ja palauttaa selkeät tuotokset.
Periaatteita:
Yksi käsite työkalua kohti. Älä tee 12 eri asiaa suorittavaa manage_customer-työkalua. Käytä rajattuja työkaluja: search_customers, get_customer, update_customer_email ja archive_customer.
Sopiva tarkkuustaso. Liian hienojakoinen työkalu vaatii monta kutsua, liian karkea ei anna tarkkaa hallintaa. Tavoittele ihmisen nimeämiä toimintoja.
Toimintaverbit. Käytä nimeä search_documents, älä documents. Nimen pitää kertoa toiminta.
Luku- ja kirjoitustyökalujen erottelu. Lukeminen on turvallisempaa, kirjoittaminen aiheuttaa sivuvaikutuksia. Erota ne nimissä (list_x ja create_x) ja vaadi kirjoituksessa esimerkiksi vahvistus ja idempotenssiavain.
Yhdistä hyödyllisesti. get_customer_profile, joka palauttaa asiakkaan, viimeaikaiset tilaukset ja tukipyynnöt yhdellä kutsulla, voi olla kolmea erillistä kutsua parempi.
Asiakastukijärjestelmää tarjoavalle palvelimelle 8-15 työkalua voi olla sopiva määrä. Enemmän on yleensä liikaa.
Malli 2: skeemasuunnittelu
Jokaisella työkalulla on syöteskeema LLM:n antamille parametreille ja tuotos. Skeemat eivät vain validoi, vaan tekevät kehotesuunnittelua.
Zod-syöteskeema:
const searchCustomersSchema = z.object({
query: z.string().describe(
"Search term: name, email, or company. Be specific to avoid too many matches."
),
limit: z.number().int().min(1).max(50).default(10).describe(
"Maximum results to return. Default 10, max 50."
),
filters: z.object({
tier: z.enum(["free", "pro", "enterprise"]).optional().describe(
"Filter to specific customer tier"
),
status: z.enum(["active", "trial", "churned"]).optional().describe(
"Filter by customer status"
),
}).optional(),
});
Huomaa:
- Jokaisella kentällä on
.describe(), jonka LLM lukee. - Enum-arvot ovat täsmällisiä, ja vapaamuotoisia merkkijonoja rajoitetaan mahdollisuuksien mukaan.
- Oletusarvot ovat järkeviä.
- Minimi-, maksimi- ja pituusrajoitteet ovat täsmällisiä.
- Valinnaiset ja pakolliset arvot erottuvat.
”Search term” ei auta, mutta täsmällinen kuvaus nimestä, sähköpostista tai yrityksestä ja liian monien osumien välttämisestä ohjaa LLM:ää hyödyllisesti.
Malli 3: tuotoksen muoto
LLM näkee tuotoksen ja toimii sen perusteella. Hyvä tuotosmuoto parantaa toimintaa merkittävästi.
Rakenteinen tuotos.
type SearchResult = {
customers: Customer[];
total_matches: number;
truncated: boolean;
next_page_cursor?: string;
};
Kontekstin kanssa.
{
customers: [...],
total_matches: 47,
truncated: true,
next_page_cursor: "abc",
message: "Found 47 matches; showing first 10. Use next_page_cursor to get more."
}
Ihmisen luettavissa oleva message-kenttä ohjaa LLM:ää.
Virheet hallitusti käsiteltyinä.
{
error: "ambiguous_query",
message: "Search term 'john' matched 247 customers. Please be more specific.",
suggestion: "Try including a company name or email domain.",
partial_results: [...] // top 3 by relevance, optional
}
Virhe on koneellisesti luettava mutta sisältää myös LLM:lle tarkoitetun viestin ja ehdotuksen. Malli voi pyytää tarkennusta tai täsmentää hakua.
Sopivan kokoinen.
10,000 tietueen palauttava työkalu on käyttökelvoton. Sisältö ei mahdu kontekstiin eikä LLM käyttäisi sitä hyvin. Sivuta, katkaise tai tiivistä aina. Palauta päätökseen tarvittava määrä, älä kaikkea.
Malli 4: virhesemantiikka
Työkalut epäonnistuvat. Virheen viestintätapa ratkaisee, palautuuko LLM hallitusti vai pahentaako se virhettä.
Virheluokat.
type ToolError =
| { type: "validation"; message: string; field?: string }
| { type: "auth"; message: string }
| { type: "not_found"; message: string; suggestion?: string }
| { type: "conflict"; message: string; resolution?: string }
| { type: "rate_limit"; message: string; retry_after_seconds: number }
| { type: "service_unavailable"; message: string; retryable: boolean }
| { type: "internal"; message: string; trace_id: string };
Luokilla on eri semantiikka, joten LLM:n pitää toimia eri tavoin:
validation: korjaa syöte ja yritä uudelleen.not_found: kerro käyttäjälle tai kokeile toista hakua.conflict: pyydä ratkaisu.rate_limit: odota ja yritä uudelleen.service_unavailable: käytä varavaihtoehtoa tai ilmoita käyttäjälle.internal: lopeta ja näytä virhe käyttäjälle.
Näiden dokumentointi tekee LLM:stä kyvykkäämmän.
Virheen muotoilu.
Palauta rakenteinen virhe sekä selkeä, toimintakelpoinen viesti:
{
error: {
type: "validation",
message: "The email address is not in a valid format.",
field: "email",
suggestion: "Provide a valid email address like 'name@example.com'."
}
}
Vältä:
{
error: "Invalid input"
}
Ensimmäisestä LLM voi palautua. Toinen jättää sen arvailemaan.
Malli 5: tunnistautuminen ja valtuutus
Tuotannon MCP-palvelin tarvitsee tunnistautumisen. Kaikki palvelimen tavoittavat voivat muuten käyttää työkaluja, mikä on lähes aina ongelma.
Tunnistautuminen: kuka kutsuu?
Tavallisia tapoja:
- API-avain. Yksinkertainen ja yleinen palvelujen välillä. Myönnä kutsujakohtaisesti ja kierrätä säännöllisesti.
- OAuth. Monen käyttäjän järjestelmiin, joissa loppukäyttäjät valtuuttavat agentteja. Monimutkaisempi mutta usein oikea ratkaisu.
- mTLS. Korkean tietoturvan ympäristöihin, joissa molemmat osapuolet käyttävät TLS-varmenteita.
Toteutus riippuu siirtotavasta. HTTP-liikenteessä tunnista pyyntö Express-, Hono- tai Fastify-väliohjelmistossa ennen MCP-käsittelijää ja tallenna kutsuja pyyntöön:
// Express-style middleware in front of the MCP HTTP endpoint.
app.use("/mcp", async (req, res, next) => {
const apiKey = req.header("x-api-key");
const caller = await authenticate(apiKey);
if (!caller) return res.status(401).send("Unauthorized");
(req as any).caller = caller;
next();
});
Hae työkalukäsittelijässä kutsuja kutsukohtaisesta extra-kontekstista, älä raakaotsakkeista. stdio ei sisällä HTTP-otsakkeita, joten tunnistautuminen tulee yleensä prosessin ympäristöstä tai asetustiedostoista.
Valtuutus: mitä kutsuja saa tehdä?
Mitä työkaluja tunnistettu kutsuja saa käyttää ja mihin tietoihin?
function authorize(caller: Caller, tool: string, params: any): boolean {
// Caller-level: can this caller use this tool at all?
if (!caller.tools.includes(tool)) return false;
// Data-level: is this caller authorized for this specific data?
if (params.tenant_id && params.tenant_id !== caller.tenant_id) return false;
return true;
}
Älä anna LLM:n tehdä valtuutuspäätöksiä, sillä sitä voidaan manipuloida. Valtuutus kuuluu palvelimelle, ja LLM näkee vain sallitut tiedot.
Usean asiakkaan järjestelmässä jokainen työkalukutsu rajataan asiakkaaseen. Asiakas määräytyy tunnistautumisesta, ei LLM:n antamasta parametrista.
Malli 6: idempotenssi
Idempotenssi on välttämätön kirjoitustoiminnoille. LLM voi yrittää uudelleen tai kutsua samaa työkalua kahdesti. Ilman idempotenssia syntyy kaksoiskappaleita.
Idempotenssiavaimet.
Työkalu vastaanottaa idempotency_key-parametrin. Palvelin tarkistaa, onko avain jo nähty. Jos on, se palauttaa välimuistituloksen. Muuten se suorittaa toiminnon ja tallentaa tuloksen.
async function createInvoice(params: {
amount: number;
customer_id: string;
idempotency_key: string;
}) {
const cached = await idempotencyStore.get(params.idempotency_key);
if (cached) return cached;
const invoice = await actuallyCreateInvoice(params);
await idempotencyStore.set(params.idempotency_key, invoice, { ttl: 86400 });
return invoice;
}
Ohjaa LLM:ää työkalukuvauksessa:
"For each unique invoice you create, generate a UUID and pass it as idempotency_key. If you need to retry the operation, use the same UUID to avoid duplicate creation."
Malli 7: suoratoisto
Pitkäkestoisissa tai suuria tuotoksia palauttavissa työkaluissa suoratoisto parantaa käyttökokemusta. MCP tukee työkalukäsittelijän edistymisilmoituksia kutsukohtaisen extra-argumentin kautta:
server.registerTool(
"long_running_task",
{ description: "...", inputSchema: { ... } },
async (input, extra) => {
await extra.sendNotification({
method: "notifications/progress",
params: { progressToken: extra._meta?.progressToken, progress: 0, message: "Starting..." },
});
for (const step of steps) {
await doStep(step);
await extra.sendNotification({
method: "notifications/progress",
params: {
progressToken: extra._meta?.progressToken,
progress: step.index / steps.length,
message: step.name,
},
});
}
return { content: [{ type: "text", text: JSON.stringify({ result: finalResult }) }] };
}
);
Käytä suoratoistoa:
- Pitkissä toiminnoissa (>5 sekuntia).
- Suurissa tuotoksissa, jotta LLM voi aloittaa käsittelyn ennen valmistumista.
- Toiminnoissa, joiden välitulokset kannattaa näyttää.
Älä suoratoista nopeita ja yksinkertaisia toimintoja. Se lisää monimutkaisuutta ilman hyötyä.
Malli 8: välimuisti
Monet työkalukutsut hakevat samoja tietoja toistuvasti. Välimuisti parantaa suorituskykyä ja vähentää taustajärjestelmän kuormaa.
Paikallinen välimuisti. Prosessinsisäinen LRU-tyyppinen välimuisti kuumalle tiedolle.
Hajautettu välimuisti. Redis tai vastaava palvelininstanssien yhteiseksi välimuistiksi.
Invalidointi. Kun tieto muuttuu, poista asiaankuuluvat tietueet. Tämä on vaikea osa.
TTL-arvot. Tietueet vanhenevat määritetyn ajan jälkeen. Säädä tietotyypin mukaan: asiakasprofiilit voivat säilyä tunteja, hinnat minuutteja.
Välimuisti auttaa vain toistuvissa kutsuissa. MCP-palvelimissa niitä syntyy usein, koska agentit viittaavat istunnon aikana samoihin kohteisiin.
Malli:
async function getCustomerCached(id: string) {
const cached = await cache.get(`customer:${id}`);
if (cached) {
metrics.increment("cache.hit");
return cached;
}
metrics.increment("cache.miss");
const customer = await db.getCustomer(id);
await cache.set(`customer:${id}`, customer, { ttl: 300 });
return customer;
}
Malli 9: nopeusrajoitus
LLM-agentit voivat toimia yllättävän aggressiivisesti: jäädä silmukkaan, yrittää uudelleen ja hajauttaa kutsuja. Huonosti toimiva agentti voi aiheuttaa palvelunestotilanteen taustajärjestelmälle.
Kutsujakohtainen nopeusrajoitus on välttämätön:
const limiter = new RateLimiter({
windowMs: 60_000,
max: 100 // 100 calls/minute per caller
});
server.setRequestHandler(CallToolRequestSchema, async (request, context) => {
if (await limiter.exceeded(context.caller.id)) {
return errorResponse("rate_limit", "Too many requests");
}
// ...
});
Yleisen rajan lisäksi tarvitaan työkalukohtaisia rajoja. Kalliita työkaluja tulee rajoittaa tiukemmin.
Käytä seurauksellisissa toiminnoissa, kuten tietueiden luonnissa ja viestien lähettämisessä, tiukempia rajoja tai nimenomaisia vahvistustyönkulkuja.
Malli 10: resurssit
MCP:n resurssit ovat vain luku -tietolähteitä, joita LLM voi selata ja joihin se voi viitata. Ne eroavat aktiivisesti kutsuttavista työkaluista.
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "doc://my-server/handbook",
name: "Employee Handbook",
mimeType: "text/markdown",
description: "Company employee handbook"
},
// ...
]
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const content = await loadResource(request.params.uri);
return { contents: [{ uri: request.params.uri, mimeType: "text/markdown", text: content }] };
});
Resurssit sopivat:
- Selattaviin viiteasiakirjoihin.
- Asetus- tai kontekstitietoihin.
- Hakutauluihin tai skeemoihin.
Resursseja luetaan, työkaluilla toimitaan. Käytä kumpaakin oikeaan tarkoitukseen.
Malli 11: havainnoitavuus
MCP-palvelin tarvitsee samat tuotantotekoälyn havainnoitavuusmallit:
- Jokainen työkalukutsu: aikaleima, kutsuja, työkalu, parametrit, tulos, viive ja tila.
- Työkalukohtaiset mittarit: määrä, p50/p95-viive ja virheprosentti.
- Kutsujakohtaiset mittarit: kuka kutsuu ja kuinka usein.
- Jäljityskonteksti: välitä jäljitystunnisteet kutsujalta taustakutsuihin.
Rakenteiset lokit:
logger.info("tool_call", {
tool: request.params.name,
caller_id: context.caller.id,
trace_id: context.trace_id,
params: redactPII(request.params.arguments),
duration_ms: duration,
status: "success"
});
Vie tiedot havainnoitavuusalustallesi.
Malli 12: versiointi
MCP-palvelin kehittyy: työkalut muuttuvat, uusia lisätään ja vanhoja poistetaan käytöstä.
Palvelimen versiointi. Server-konstruktori saa version. Kasvata sitä muutoksissa, jotta asiakkaat voivat havaita ne.
Työkalujen versiointi. Jos allekirjoitus muuttuu yhteensopimattomasti, versioi työkalu esimerkiksi nimellä search_customers_v2. Säilytä vanha versio siirtymäajan.
Skeeman kehitys. Valinnaisia kenttiä voi lisätä turvallisesti. Kentän poistaminen tai tyypin muuttaminen on rikkova muutos.
Käytöstä poistaminen. Merkitse työkalun kuvaukseen: ”DEPRECATED: use search_customers_v2 instead.”
Versiointi on välttämätöntä usean asiakkaan tuotantopalvelimissa. Vain sisäisessä käytössä voi joustaa enemmän.
Malli 13: testaus
Miten MCP-palvelinta testataan?
Yksikkötestit. Testaa jokaisen työkalun logiikka mallinnetuilla riippuvuuksilla tavalliseen TypeScript-tapaan.
Skeematestit. Varmista validointi ja reunatapausten, kuten puuttuvien kenttien ja väärien tyyppien, käsittely.
Integraatiotestit. Käynnistä palvelin, lähetä todellisia MCP-pyyntöjä ja varmista vastaukset. @modelcontextprotocol/sdk sisältää testityökaluja.
Päästä päähän todellisella LLM:llä. Vaikein mutta arvokkain testi. Anna LLM:n suorittaa realistisia tehtäviä MCP-palvelimella, varmista oikea työkalujen käyttö ja löydä kuvausongelmat.
Päästä päähän -testi (pseudokoodi; tarkka asiakaskytkentä riippuu Anthropicin tai OpenAI:n TypeScript SDK:sta tai MCP:tä tukevasta kehyksestä):
// Start your MCP server as a child process or in-memory transport.
const server = await startTestServer();
// Drive an LLM with the MCP tools attached. The exact API depends on the client.
const result = await runAgent({
mcpServer: server,
systemPrompt: "You are a customer service agent...",
userMessage: "Find the customer Alice and check her open tickets",
});
// Inspect the tool calls captured by the server during the run.
expect(server.callLog.map((c) => c.name)).toEqual([
"search_customers",
"list_tickets",
]);
Päästä päähän -testit havaitsevat työkalukuvausten ongelmia, joita yksikkötestit eivät löydä.
Malli 14: käyttöönotto
Missä MCP-palvelin suoritetaan?
Stdio paikallisesti. Palvelin toimii prosessina, jonka asiakas käynnistää. Sopii työpöytäsovelluksiin, kuten Claude Desktopiin ja Cursoriin, sekä paikallisiin työkaluihin.
Streamable HTTP etänä. Palvelin toimii verkkopalveluna. Sopii palveluna tarjottuihin ratkaisuihin, yhteiseen infrastruktuuriin ja usean asiakkaan käyttöön.
Tuotantopalvelimissa:
- Streamable HTTP on yleensä oikea valinta.
- Ota käyttöön kuten verkkopalvelu: kontit, kuormantasaus ja automaattinen skaalaus.
- TLS on pakollinen.
- Käyttöönottosalusta tarvitsee terveystarkistukset.
- Käynnissä olevat pyynnöt edellyttävät hallittua sammutusta.
Malli 15: tietoturva
MCP-palvelimet tarjoavat LLM-malleille kyvykkyyksiä, ja LLM:iä voidaan manipuloida. Tietoturvavaikutukset:
Kehoteinjektio työkalusyötteissä. Käyttäjän pyyntö voi yrittää huijata LLM:ää kutsumaan työkaluja vahingollisesti. Suojaukset:
- Selkeät työkalukuvaukset.
- Palvelinpuolen valtuutus riippumatta LLM:n päättämistä parametreista.
- Seurauksellisten toimintojen vahvistukset.
Tietojen vuotaminen. Tietoa palauttavia työkaluja voidaan väärinkäyttää ja LLM huijata palauttamaan arkaluonteista tietoa. Suojaukset:
- Valtuutustarkistukset.
- Tiedon käytön lokitus käyttäjittäin.
- Epätavallisten käyttötapojen havaitseminen.
Resurssien kuluttaminen loppuun. Taustaresursseja kuluttavia työkaluja voidaan väärinkäyttää. Suojaukset:
- Nopeusrajoitus.
- Työkalukutsukohtaiset resurssirajat.
- Katkaisijat taustajärjestelmän heikentyessä.
Injektio työkalutuotoksessa. Työkalun palauttama teksti voi manipuloida sitä lukevaa LLM:ää. Suojaukset:
- Puhdista tuotokset mahdollisuuksien mukaan.
- Suhtaudu varoen käyttäjien tuottamaa sisältöä palauttaviin työkaluihin.
Nämä ovat todellisia hyökkäyspintoja. Käsittele MCP-palvelinta kuten mitä tahansa tuotantorajapintaa: käytä kerroksellista suojausta.
Kokonainen esimerkki: pieni mutta todellinen MCP-palvelin
Kootaan pieni CRM-palvelin:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import { db, cache, logger, authenticate } from "./infra.js";
const server = new McpServer({
name: "crm-server",
version: "1.0.0",
});
// === Tool: search_customers ===
server.registerTool(
"search_customers",
{
description: "Search customers by name, email, or company.",
inputSchema: {
query: z.string().describe("Name, email, or company"),
limit: z.number().int().min(1).max(50).default(10),
},
},
async ({ query, limit }, extra) => {
const auth = await authenticate(extra);
const cacheKey = `search:${auth.tenant_id}:${query}:${limit}`;
const cached = await cache.get(cacheKey);
if (cached) return cached;
const customers = await db.searchCustomers({
tenant_id: auth.tenant_id,
query,
limit,
});
const result = {
content: [{
type: "text" as const,
text: JSON.stringify({
customers,
total_matches: customers.length,
truncated: customers.length === limit,
message:
customers.length === limit
? `Showing first ${limit}; there may be more matches.`
: `Found ${customers.length} customer(s).`,
}),
}],
};
await cache.set(cacheKey, result, { ttl: 60 });
logger.info("search_customers", { tenant: auth.tenant_id, query, results: customers.length });
return result;
}
);
// === Tool: get_customer ===
server.registerTool(
"get_customer",
{
description: "Fetch a single customer by id.",
inputSchema: { customer_id: z.string() },
},
async ({ customer_id }, extra) => {
const auth = await authenticate(extra);
const customer = await db.getCustomer(auth.tenant_id, customer_id);
if (!customer) {
return {
isError: true,
content: [{
type: "text" as const,
text: `Customer ${customer_id} not found. Use search_customers to find by name or email.`,
}],
};
}
return { content: [{ type: "text" as const, text: JSON.stringify({ customer }) }] };
}
);
// === Tool: update_customer_email (with idempotency) ===
server.registerTool(
"update_customer_email",
{
description: "Update a customer's email; pass the same idempotency_key on retry.",
inputSchema: {
customer_id: z.string(),
new_email: z.string().email(),
idempotency_key: z
.string()
.describe("UUID for this update; pass the same value on retry to prevent duplicates"),
},
},
async (params, extra) => {
const auth = await authenticate(extra);
// ... idempotency check, validation, update
return { content: [{ type: "text" as const, text: "ok" }] };
}
);
// ... more tools ...
// Wire up a remote transport (Streamable HTTP) on a chosen port via your HTTP server of choice.
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID() });
await server.connect(transport);
Tämä on lähtörakenne. Lisää havainnoitavuus, nopeusrajoitus, työkalut ja huolellisemmat skeemat. Perusta on valmis.
Mikä erottaa tuotantopalvelimen demosta
MCP on pieni protokolla, mutta tuotantotasoisen palvelimen rakentaminen on todellista suunnittelutyötä. Vastineeksi mikä tahansa LLM-agentti voi käyttää palveluasi standardoidulla integraatiolla ilman mallikohtaista kytkentää.
Olennaisia malleja ovat rajattu työkalusuunnittelu, kehotetietoiset skeemat, rakenteinen virhesemantiikka, vankka tunnistautuminen, idempotenssi, havainnoitavuus ja tietoturva. Jonkin ohittaminen tuottaa MCP-palvelimen, joka epäonnistuu tuotannossa.
Rakenna ne mukaan, testaa todellisilla LLM-malleilla ja kehitä työkalukuvauksia. Tuloksena on palvelu, jota LLM käyttää yhtä sujuvasti kuin ihminen ja joka skaalautuu nopeasti kasvavan tekoälyagenttien joukon mukana.



