MCP (Model Context Protocol) on toteutettu useissa agenttiasiakkaissa ja kehitystyökaluissa. Laaja käyttö tekee siitä hyödyllisen integraatiorajan, mutta mikään palvelin ei muutu siirrettäväksi, turvalliseksi tai tuotantovalmiiksi ilman asiakas- ja uhkamallitestausta.
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ällä artikkelilla on kaksi tarkoituksella erillistä tuotosta: suoritettava stdio-vähimmäispalvelin ja tuotantosuunnittelun tarkistuslista. Koodikatkelmat eivät yhdessä muodosta käyttöönotettua tunnistautuvaa palvelua. Vähimmäiskoodi on kirjoitettu paketille @modelcontextprotocol/sdk@1.30.0. Nykyinen v2 SDK käyttää erillisiä paketteja (@modelcontextprotocol/server, @modelcontextprotocol/node ja sovelluskehyskohtaiset sovittimet), joten aloita uusi v2-toteutus virallisesta palvelinoppaasta. Älä sekoita v1- ja v2-tuonteja.
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. Lue lukittua versiota koskeva virallinen määrittely. Siirtotapoina ovat stdio paikallisille prosesseille ja Streamable HTTP etäpalvelimille. Vanhempi HTTP+SSE-siirtotapa poistettiin käytöstä 2025-03-26-versiossa. HTTP-valtuutusmäärittely perustuu OAuthiin. Sovelluksen yhdyskäytävä voi lisätä muita tunnistetapoja, mutta API-avain ei korvaa väitettä MCP:n valtuutusmäärittelyn noudattamisesta.
Palvelimen tehtävä on tarjota LLM-malleille hyödyllisiä kyvykkyyksiä löydettävässä ja käytettävässä muodossa.
Perusrakenne
Luo puhdas hakemisto ja lukitse sama pääversio kuin tässä esimerkissä:
npm init -y
npm install @modelcontextprotocol/sdk@1.30.0 zod@3
npm install --save-dev typescript@5 @types/node
Lisää sitten v1-version korkean tason McpServer-rajapintaan perustuva vähimmäispalvelin:
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 jokainen taustalla oleva päätepiste mekaanisesti työkaluna. Laaja joukko epäolennaisia työkaluja kasvattaa skeemakontekstia ja voi lisätä valintavirheitä joillakin malleilla ja tehtävillä. Vertaa tehtäväkeskeisiä työkaluja päätepisteitä vastaavaan vertailutasoon ja tarjoa vain istunnossa tarvittava valtuutettu osajoukko.
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.
Yleispätevää oikeaa työkalumäärää ei ole. Tarjoa vain nykyiseen tehtävään liittyvät ja valtuutetut työkalut ja mittaa valintavirheitä, kun lisäät tai poistat työkaluja.
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.
Rajaamaton tietuejoukko voi ylittää konteksti-, kustannus-, viive- ja tietojen paljastumisrajat. Aseta enimmäismäärä, sivuta vakailla kohdistimilla ja palauta vain tehtävässä tarvittavat ja valtuutetut kentät. Tiivistelmät ovat johdettua tietoa ja tarvitsevat alkuperätiedon, kun täsmällisillä tietueilla on merkitystä.
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
Etänä toimiva tuotannon MCP-palvelin tarvitsee tavallisesti tunnistetut ja valtuutetut kutsujat. Paikallinen stdio-palvelin puolestaan perii sen käynnistävän prosessin oikeudet. Pelkkä verkkoyhteys ei saa koskaan antaa työkalujen käyttöoikeutta.
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ä bearer-tunniste on validoitava ennen MCP-pyynnön välittämistä. Virallinen v1-version bearer-tunnistautumisen väliohjelmisto liittää validoidun authInfo-tiedon käsittelijän extra-tietoihin. Käytä v2:ssa ajantasaisia resurssipalvelimen ja pyyntötilan rajapintoja. Älä keksi sovelluskohtaista context.caller-kenttää.
server.registerTool("who_am_i", {
description: "Return the authenticated caller identity.",
inputSchema: {},
}, async (_input, extra) => {
if (!extra.authInfo) {
return { isError: true, content: [{ type: "text", text: "Authentication required" }] };
}
const subject = String(extra.authInfo.extra?.sub ?? extra.authInfo.clientId);
return { content: [{ type: "text", text: JSON.stringify({ subject }) }] };
});
Tämä käsittelijä olettaa, että HTTP-siirtotapa on jo suorittanut virallisen tunnistautumisväliohjelmiston. Rekisteröinti tunnistautumattomaan stdio-palvelimeen ei luo tunnistautumista. stdio-siirtotavassa ei ole HTTP:n bearer-otsakkeita, vaan käyttöä hallitaan tavallisesti paikallisen prosessirajan, asetusten, tiedostojärjestelmäoikeuksien ja käynnistävän asiakkaan avulla.
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-avaimen. Palvelimen on varattava avain atomisesti pysyvässä tallennustilassa ja sidottava se tunnistettuun toimijaan, työkalun nimeen ja normalisoidun pyynnön tiivisteeseen. Erillinen luku ja sitä seuraava kirjoitus aiheuttavat rinnakkaisuudessa kilpailutilanteen.
async function createInvoice(params: {
amount: number;
customer_id: string;
idempotency_key: string;
}) {
return database.transaction(async (tx) => {
const claim = await tx.claimIdempotencyKey({
principal_id: currentPrincipal.id,
tool: "create_invoice",
key: params.idempotency_key,
request_hash: hashCanonicalRequest(params),
});
if (claim.request_hash_mismatch) throw new Error("Idempotency key reused for different input");
if (claim.completed_response) return claim.completed_response;
const invoice = await tx.createInvoice(params);
await tx.completeIdempotencyClaim(claim.id, invoice);
return invoice;
});
}
Ohjaa LLM:ää työkalukuvauksessa:
"Luo jokaista yksilöllistä laskua kohti UUID ja anna se arvona idempotency_key. Jos joudut yrittämään toimintoa uudelleen, käytä samaa UUID:ta, jotta laskuja ei synny kahteen kertaan."
Malli 7: suoratoisto
Päätä pitkäkestoisessa toiminnossa ensin, pitäisikö sen olla pysyvä asynkroninen työ. MCP:n edistymisilmoituksista on hyötyä vain, jos asiakas antaa edistymistunnisteen ja ylläpitää yhteyttä. Käsittelijärajapinta muuttui SDK:n pääversioiden välillä, joten kopioi esimerkki lukitsemasi version dokumentaatiosta äläkä tästä artikkelista.
Katso edistymis- ja tehtävärajapinnat lukitun SDK-version palvelinesimerkistä. Älä koskaan lähetä edistymisilmoitusta ilman määriteltyä tunnistetta äläkä käytä hetkellistä ilmoitusta seurauksellisen toiminnon ainoana tietueena.
Käytä suoratoistoa:
- Toiminnoissa, joissa välivaiheiden edistyminen on asiakkaalle merkityksellistä.
- 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, jos samat valtuutetut kutsut toistuvat. Sisällytä asiakas ja valtuutuksen laajuus välimuistiavaimiin äläkä tallenna arkaluonteisia vastauksia eri kutsujien yhteiseen välimuistiin.
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.
Etäpalvelussa tarvitaan nopeusrajoitus tunnistetun toimijan, asiakkaan, työkalun ja resurssikustannuksen mukaan. Lue v1-version työkalukäsittelijässä identiteetti validoidusta extra.authInfo-tiedosta, älä olemattomasta context.caller-kentästä:
const limiter = new RateLimiter({
windowMs: 60_000,
max: 100 // 100 calls/minute per caller
});
server.registerTool("expensive_report", {
description: "Build an authorized report.",
inputSchema: { report_id: z.string() },
}, async ({ report_id }, extra) => {
if (!extra.authInfo) return toolError("Authentication required");
const subject = String(extra.authInfo.extra?.sub ?? extra.authInfo.clientId);
if (await limiter.exceeded({ subject, tool: "expensive_report" })) {
return toolError("Rate limit exceeded; retry later");
}
return buildAuthorizedReport(subject, report_id);
});
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
LLM-sovellusten kutsu- ja jäljitystason malleja käsitellään tarkemmin havainnoitavuutta koskevassa artikkelissa. Instrumentoi MCP-palvelimessa:
- Jokainen työkalukutsu: aikaleima, tunnistettu toimija tai pseudonymisoitu tunniste, työkalu, peitetty argumenttien tiivistelmä, tulosluokka, 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.
Sovellustason pseudokoodi, ei SDK:n käsittelijäkonteksti:
logger.info("tool_call", {
tool: request.params.name,
caller_id: authenticatedPrincipal.id,
trace_id: currentTraceId,
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. McpServer-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 standardoitu ehdokas etäsiirtotavaksi. Varmista asiakastuki, istuntosuunnittelu, valtuutus, origin- ja host-suojaus, välityspalvelimet, aikakatkaisut ja skaalautuminen.
- 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.
Kokoamishahmotelma: ei kokonainen HTTP-palvelin
Seuraava katkelma osoittaa, miten asiakasrajaus kuuluu jokaiseen työkaluun. Se on tarkoituksella epätäydellinen: db, cache, logger, authenticate, HTTP-reitti, bearer-tunnistautumisen väliohjelmisto, elinkaaren hallinta ja testit ovat sovelluskoodia. Älä kopioi katkelmaa ja kutsu tulosta käyttöönotetuksi.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.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 ...
// Deliberately omitted: authenticated Streamable HTTP route and transport lifecycle.
// Start from the official example for the exact pinned SDK version.
Tämä on suunnitteluhahmotelma, ei suoritettava päätepiste. Tuotantototeutus tarvitsee edelleen lukitun SDK-version virallisen siirtotapaesimerkin, host-otsakkeen ja DNS-uudelleensidonnan suojauksen, tarvittaessa OAuth-resurssimetatiedot, TLS:n käyttöönottorajalla, valtuutustestit, idempotenssin tallennuksen, telemetrian peittämisen, rajat, hallitun sammutuksen ja testatun asiakasmatriisin.
Mikä erottaa tuotantopalvelimen demosta
MCP on rajattu protokolla, mutta tuotantotasoisen palvelimen rakentaminen on edelleen API- ja tietoturvasuunnittelua. Standardien kanssa yhteensopiva palvelu voi vähentää asiakaskohtaista integraatiotyötä, mutta yhteensopivuusnäyttö koskee vain testimatriisin asiakkaita, versioita, siirtotapoja, tunnistautumispolkua ja työkaluja.
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 agentit voivat kutsua luotettavasti ilman mallikohtaista integraatiota.



