Design MCP-værktøjer, som LLM'er faktisk bruger korrekt
Avanceret12 min læsningAI til virksomheder

Design MCP-værktøjer, som LLM'er faktisk bruger korrekt

De fleste MCP-værktøjer, vi ser, er teknisk korrekte, men praktisk talt ubrugelige. LLM'er ignorerer dem, bruger dem forkert eller foretager unødvendige kald. Her er principperne for at designe værktøjer, som LLM'er tager naturligt i brug, med eksempler på almindelige fejl og løsninger.

Hvad du bør kunne

Værktøjsdesign til LLM'er minder mere om UX-design end API-design. LLM'en er din bruger. Værktøjsbeskrivelsen er din grænseflade. Gode værktøjer bruges ofte korrekt; dårlige værktøjer ignoreres, misbruges eller sættes uhensigtsmæssigt sammen. Her er de mønstre, der betyder noget.

AI Expert TeamUdgivet: 15. maj 2026
Gemt kun i denne browser.
I denne artikel

Du har bygget en MCP-server. Værktøjerne fungerer. Du forbinder en LLM-agent. Når du ser den arbejde, opdager du, at den ignorerer værktøjerne, kalder dem med uforklarlige parametre, bliver i tvivl om, hvilket værktøj den skal bruge, og sammensætter dem i mærkelige rækkefølger.

Det er kløften mellem “værktøjer, der findes” og “værktøjer, som LLM’er bruger korrekt”. Her går indsatsen i de fleste MCP-servere tabt. Virksomheder bygger stærke funktioner, udstiller dem som værktøjer og ser derefter LLM’er fejle i den praktiske anvendelse.

Den nyttige omformulering er at betragte værktøjsdesign som UX-design, hvor LLM’en er brugeren. Værktøjsbeskrivelsen er brugergrænsefladen. Skemaet er formularen. Fejlmeddelelserne er tilbagemeldingen. Gør du disse dele rigtigt, arbejder LLM’en effektivt. Gør du dem forkert, bliver din avancerede backend usynlig for agenten.

Denne artikel gennemgår principperne med konkrete eksempler på, hvad der virker, og hvad der ikke gør.

Princip 1: Værktøjsnavne kommunikerer hensigten

Værktøjets navn er det første, LLM’en ser. Det skal beskrive, hvad værktøjet gør, i handlingstermer.

Dårligt:

  • customers (navn, ingen handling)
  • process_customer (vagt)
  • do_x (meningsløst)

Bedre:

  • search_customers (klar handling)
  • get_customer_by_id (specifik operation)
  • update_customer_email (specifik ændring)

Hvorfor det betyder noget: LLM’er gennemgår værktøjslister for at finde relevante muligheder. Et beskrivende navn gør det muligt hurtigt at finde det rette værktøj. Et vagt navn tvinger modellen til at læse beskrivelsen grundigt (hvilket den ikke altid gør).

Et nyttigt mønster er standardiserede handlingsverber:

  • list_*, search_*, get_* til læsning.
  • create_*, update_*, delete_* til skrivning.
  • analyze_*, summarize_* til beregning.

Ensartethed på tværs af serveren hjælper LLM’en med at danne en mental model.

Princip 2: Beskrivelser er prompts

Værktøjsbeskrivelsen er den vigtigste tekst på serveren. LLM’en bruger den til at beslutte, om og hvordan værktøjet skal anvendes.

Dårlig beskrivelse:

search_customers: Search the customer database.

Bedre beskrivelse:

search_customers: Find customers by name, email, or company. Returns up to 10 matching customers with their basic info. Use this when you need to identify a customer the user is referring to. For exact lookups by ID, use get_customer_by_id instead.

Bemærk, hvad den bedre version gør:

  • Beskriver input (“efter navn, e-mail eller virksomhed”).
  • Beskriver output (“op til 10 kunder med grundlæggende oplysninger”).
  • Angiver, hvornår værktøjet skal bruges (“når du skal identificere en kunde, som brugeren henviser til”).
  • Angiver, hvornår værktøjet ikke skal bruges (“Brug get_customer_by_id i stedet til præcise opslag efter id”).

Afsnittet om, hvornår værktøjet ikke skal bruges, er afgørende. Uden det kan LLM’en kalde search_customers, selv om get_customer_by_id er mere passende.

Princip 3: Parameterbeskrivelser betyder noget

Hver parameter skal have en beskrivelse. Undgå at stole på parameternavnet alene.

Dårligt:

{
  customer_id: string,
  fields: string[]
}

Bedre:

{
  customer_id: string,  // "The customer's unique identifier. Get this from search_customers or from explicit user input."
  fields: string[]      // "Specific fields to return. Available: name, email, phone, tier, created_at, last_active. If not specified, returns name and email."
}

Beskrivelserne:

  • Fortæller LLM’en, hvordan værdien findes.
  • Angiver tilladte værdier, hvor det er relevant.
  • Angiver standardværdier.

Princip 4: Fejl skal lede til genopretning

Når et værktøj fejler, leder fejlmeddelelsen LLM’en videre. Vage fejl fører til forvirrede agenter.

Dårlig fejl:

{ "error": "Invalid input" }

Bedre fejl:

{
  "error": "validation_error",
  "message": "The email '...' is not in a valid format. It must be like 'name@example.com'.",
  "field": "email",
  "suggestion": "Ask the user for a valid email address."
}

LLM’en ved nu:

  • Hvad der gik galt (valideringsfejl på e-mailfeltet).
  • Hvordan fejlen rettes (brug et gyldigt e-mailformat).
  • Hvad næste skridt er (spørg brugeren).

Sammenlign agentens adfærd ved de to fejl. Den første kan gentage det samme kald (spild), give op (dårlig UX) eller hallucinere et gyldigt input. Den anden fører til en klar brugerinteraktion.

Princip 5: Output former den næste handling

Værktøjets output bestemmer, hvad LLM’en gør bagefter. Outputdesign påvirker agentens adfærd.

Dårligt output for en søgning:

[
  {"id": "c1", "n": "John", "e": "john@..."},
  {"id": "c2", "n": "Jane", "e": "jane@..."}
]

Bedre output:

{
  "customers": [
    {"id": "c1", "name": "John Smith", "email": "john@example.com", "tier": "pro"},
    {"id": "c2", "name": "Jane Doe", "email": "jane@example.com", "tier": "free"}
  ],
  "total_found": 2,
  "summary": "Found 2 customers matching 'john'. Note that one is named 'Jane Doe' but has 'john' in their email."
}

Det bedre output:

  • Bruger læselige feltnavne.
  • Indeholder kontekstmetadata (total_found).
  • Indeholder et summary-felt i naturligt sprog, som hjælper LLM’en med at formulere det næste svar.

Opsummeringsfeltet er effektivt – det svarer til at give LLM’en et diskret vink om, hvordan resultatet skal fortolkes.

Princip 6: Et værktøj, én ting

Værktøjer med flere formål forvirrer LLM’er. LLM’en skal både beslutte, om værktøjet skal bruges, og hvilken tilstand det skal køre i.

Forvirrende:

manage_customer:
  - mode: "search" | "get" | "update" | "delete"
  - params: depends on mode

LLM’en skal vælge tilstanden og vælger ofte forkert. Endnu værre er parameterskemaet komplekst, fordi det varierer efter tilstand.

Bedre: adskilte værktøjer.

search_customers: search by name/email/company
get_customer: get details by ID
update_customer: update specific fields
delete_customer: archive a customer

Hvert værktøj er entydigt. LLM’en vælger ét ud fra hensigten. Skemaerne er enkle.

Det giver flere værktøjer, men hvert af dem er tydeligere. LLM’en håndterer 10 klare værktøjer bedre end 3 værktøjer med flere tilstande.

Princip 7: Begræns input

Begræns de mulige input, hvor det er muligt. Enumværdier og validering modvirker LLM-hallucinationer.

Løst:

{
  status: string  // could be anything
}

Begrænset:

{
  status: "active" | "trial" | "churned" | "suspended"
}

Begrænsningen håndhæves på skemaniveau (begrænset generering forhindrer LLM’en i at producere ugyldige værdier).

Det samme gælder enumværdier for operationer, alvorlighedsgrader og typer – alt med et kendt sæt gyldige værdier.

Brug ISO 8601-format til datoer, og angiv det i beskrivelsen (“Dato i ISO 8601-format, f.eks. 2026-05-15”). Ellers producerer LLM’er datoer i vilkårlige formater.

Princip 8: Standardværdier reducerer hallucination

Når parametre har fornuftige standardværdier, skal de være valgfrie, og standarden skal anvendes på serversiden.

Dårligt:

{
  query: string,
  limit: number,  // LLM has to provide some value
  include_archived: boolean,
  sort_by: string
}

LLM’en skal vælge værdier til dem alle, og de kan være forkerte.

Bedre:

{
  query: string,
  limit: number = 10,            // sensible default
  include_archived: boolean = false,  // safe default
  sort_by: "relevance" | "name" | "created_at" = "relevance"  // most common
}

LLM’en angiver kun parametre, der er relevante for den specifikke forespørgsel. Færre parametre betyder mindre plads til forvirring.

Dokumentér standardværdierne i beskrivelsen: “Limit: Antal resultater, der returneres. Standard 10, maksimum 50.”

Princip 9: Muligheden for at kombinere værktøjer betyder noget

Værktøjer skal kunne sammensættes til arbejdsgange, som LLM’en kan bygge. Den rette detaljeringsgrad gør komplekse opgaver nemme.

Overvej en opgave: “Fortæl mig om alle åbne sager for vores top 3 kunder.”

Dårligt værktøjssæt:

get_customer_summary(customer_id): returns customer + tickets + activity all in one

LLM’en kan ikke nemt udføre “top 3”-filtreringen – værktøjet returnerer alt for én kunde ad gangen. For at løse opgaven skal LLM’en først vide, hvem de vigtigste kunder er, og derefter kalde værktøjet 3 gange.

Bedre værktøjssæt:

list_customers(sort_by="value", limit=N): returns customer summaries with priority info
list_tickets(customer_id, status): returns tickets for a customer

LLM’en kan sammensætte forløbet naturligt: Først listes de vigtigste kunder, og derefter listes åbne sager for hver af dem.

Princippet er at tænke i arbejdsgange med flere værktøjer. Værktøjer, der let kan kombineres, er anvendelige; værktøjer, der ikke kan, er ofte ubrugelige.

Princip 10: Kommunikér idempotens

Angiv idempotenskrav i beskrivelsen af skriveværktøjer:

create_invoice: Create a new invoice for a customer.
IMPORTANT: Pass an idempotency_key (a UUID you generate). If you retry this operation, use the same UUID to prevent duplicate invoices.

Parameters:
- amount: ...
- customer_id: ...
- idempotency_key: UUID to prevent duplicate creation on retry. Generate once per logical operation.

Nu ved LLM’en, at den skal generere et UUID og genbruge det ved et nyt forsøg.

Uden denne vejledning kan LLM’en enten udelade nøglen (ingen idempotens) eller generere et nyt UUID ved hvert forsøg (hvilket undergraver formålet).

Princip 11: Angiv forudsætninger og følgevirkninger

Angiv det tydeligt for værktøjer med forudsætninger eller væsentlige sideeffekter:

delete_customer: Archive a customer record. This is reversible within 30 days; after 30 days, the data is permanently deleted.

PRECONDITIONS:
- Customer must have no active subscriptions.
- Customer must have no open tickets.

If preconditions are not met, this tool returns an error indicating what to resolve first.

SIDE EFFECTS:
- All customer's contacts are also archived.
- Customer is removed from active reports.
- An audit log entry is created.

LLM’en ved nu, hvad der skal kontrolleres før kaldet, og hvad der sker bagefter. Den kan planlægge arbejdsgange med flere trin korrekt (“luk først kundens sager, og slet derefter kunden”).

Princip 12: Brug eksempler, når du er i tvivl

Medtag et eksempel i beskrivelsen af komplekse værktøjer:

analyze_funnel: Analyze a conversion funnel from event data.

Parameters:
- start_date: ISO 8601 date
- end_date: ISO 8601 date  
- steps: array of step definitions, each {event_name: string, filters?: object}

Example:
{
  "start_date": "2026-01-01",
  "end_date": "2026-01-31",
  "steps": [
    {"event_name": "signup"},
    {"event_name": "first_login"},
    {"event_name": "first_action", "filters": {"action_type": "create_project"}},
    {"event_name": "subscription_started"}
  ]
}

Eksempler lærer LLM’en strukturen bedre end skemaer alene.

Princip 13: Undgå at afsløre interne detaljer

LLM’en behøver ikke kende databasestrukturen eller interne id’er. Giv den en ren, konceptuel model.

Dårligt:

get_user_by_pk(pk: number)

LLM’en skal vide, at den skal bruge en “primærnøgle” – et databasespecifikt koncept.

Bedre:

get_user(user_id: string)

Skjul databasespecifikke koncepter. LLM’en bruger et user_id, som er et meningsfuldt begreb.

Undgå ligeledes at afsløre forældede felter, interne flag, fejlfindingsparametre eller andet, der vedrører implementeringen frem for det brugervendte koncept.

Princip 14: Undgå magiske strenge

Nogle værktøjer kræver strenge, der ligner kommandoer eller koder. De er fejlbehæftede.

Dårligt:

modify_record(record_id: string, change_string: string)
// where change_string is like "field1=value1;field2=value2"

LLM’en skal indkode ændringerne i et bestemt strengformat og vil begå fejl.

Bedre:

update_record(record_id: string, updates: { field1?: any; field2?: any; ... })

Brug strukturerede opdateringer i et objekt. LLM’en kan angive hvert felt direkte.

Princip 15: Test med rigtige LLM’er

Værktøjsbeskrivelser kan være lette at læse for mennesker og stadig forvirre LLM’er. Den eneste måde at vide det på er at teste dem.

En nyttig arbejdsgang:

  1. Byg værktøjet.
  2. Lad en LLM-agent prøve flere realistiske opgaver ved kun at bruge dine værktøjer.
  3. Observer fejl.
  4. Justér beskrivelser baseret på fejl.
  5. Gentag.

Mønstrene, du vil finde:

  • LLM’en bruger det forkerte værktøj → værktøjets navn eller beskrivelse er uklar.
  • LLM’en sender forkerte parameterværdier → parameterbeskrivelsen eller skemaet skal forbedres.
  • LLM’en giver op efter en fejl → fejlmeddelelserne skal forbedres.
  • LLM’en prøver ikke et værktøj, der ville hjælpe → værktøjet er ikke fremhævet eller navngivet korrekt.

Hver fejl peger på en specifik løsning.

Diagnose: Tegn på dårligt designede værktøjer

Nogle mønstre tyder på problemer med værktøjsdesignet:

LLM’en bruger ofte det forkerte værktøj. Du vil se den kalde search_customers, når den burde have kaldt get_customer_by_id. Løsning: Gør det tydeligt, hvilket værktøj der passer til hver situation.

LLM’en kalder mange værktøjer for at udføre én handling. Den sammenkæder 5 værktøjskald for at udføre noget, der burde kræve 1. Løsning: Måske har du brug for et sammensat værktøj på et højere niveau, eller også er detaljeringsgraden for fin.

LLM’en opgiver efter fejl. Den prøver én gang, får en fejl og fortæller brugeren, at den ikke kan hjælpe. Løsning: bedre fejlmeddelelser, der foreslår næste skridt.

LLM’en hallucinerer parameterværdier. Den opfinder user_ids, datoer og id’er. Løsning: Gør det tydeligt, hvordan gyldige værdier findes; tilføj begrænsninger og fejlhåndtering, der registrerer og forklarer problemet.

LLM’en gentager det samme fejlslagne kald. Den samme fejl opstår igen. Løsning: Fejlmeddelelsen fortæller ikke LLM’en konkret, hvad der er galt.

LLM’en bruger ikke et effektivt værktøj. Du har bygget et fremragende værktøj, men LLM’en kalder det aldrig. Løsning: Gør værktøjet lettere at opdage med et klarere navn, en bedre beskrivelse og vejledning i formen “brug dette, når …”.

Værktøjstaksonomi

En nyttig øvelse: organisér værktøjer i en taksonomi.

Read tools (safe, idempotent):
- search_customers
- get_customer_by_id
- list_tickets
- list_orders

Compute tools (no state changes):
- summarize_account_activity
- analyze_funnel
- calculate_lifetime_value

Write tools (state changes, need idempotency):
- create_customer
- update_customer_email
- create_ticket
- send_email

Destructive tools (require careful authorization):
- delete_customer
- cancel_subscription
- archive_record

Taksonomien hjælper dig med at:

  • Anvende passende sikkerhedsforanstaltninger (idempotens og bekræftelse ved destruktive handlinger).
  • Dokumentere kategorierne i systemprompts til LLM’en.
  • Opdage manglende værktøjer (har du brug for et, hvis en kategori er tom?).

En nyttig systemprompttilføjelse:

Tool categories available:
- READ tools (safe to call): search_customers, get_customer_by_id, ...
- COMPUTE tools (no side effects): summarize_account_activity, ...
- WRITE tools (side effects, include idempotency_key): create_customer, ...
- DESTRUCTIVE tools (require human confirmation): delete_customer, ...

Before calling a WRITE or DESTRUCTIVE tool, confirm with the user.

Det former, hvordan LLM’en bruger værktøjer på arbejdsgangsniveau, ikke kun i det enkelte kald.

Eksempler på almindelige forbedringer

Her er før-og-efter-eksempler, der gør principperne konkrete:

Eksempel 1: Et søgeværktøj

Før:

// search documents
{
  name: "documents",
  description: "Search documents",
  inputSchema: { query: "string" }
}

Efter:

{
  name: "search_documents",
  description: `Search internal documents (knowledge base, wiki pages, policies). 
  Returns matching documents with title, excerpt, and link. Use when the user asks about company policies, procedures, or internal documentation. Returns up to 10 most relevant matches by semantic similarity.`,
  inputSchema: {
    query: {
      type: "string",
      description: "Search query. Be specific. Good: 'remote work policy 2026'. Bad: 'documents about work'."
    },
    document_type: {
      type: "string",
      enum: ["policy", "procedure", "guide", "faq", "any"],
      default: "any",
      description: "Filter to a specific type of document."
    },
    limit: {
      type: "number",
      default: 5,
      maximum: 10,
      description: "Number of results."
    }
  }
}

Eksempel 2: Et handlingsværktøj

Før:

{
  name: "send_email",
  description: "Send an email",
  inputSchema: {
    to: "string",
    subject: "string",
    body: "string"
  }
}

Efter:

{
  name: "draft_email_to_customer",
  description: `Draft an email to a customer based on a recent interaction. The email is saved as a draft for human review before sending — it is NOT sent automatically. The user must approve drafts in their inbox.

  Use when:
  - You've identified an action requiring follow-up with the customer.
  - You have a specific reason and content for the email.
  
  Do NOT use:
  - To send marketing or promotional content.
  - Without explicit user request.
  - To respond to refund or cancellation requests (escalate to human instead).`,
  inputSchema: {
    customer_id: {
      type: "string",
      description: "Customer ID from search_customers or get_customer."
    },
    subject: {
      type: "string",
      description: "Email subject, 4-8 words, specific. Avoid generic subjects like 'Following up'."
    },
    body: {
      type: "string",
      description: "Email body, plain text. 3-5 sentences. Personal, specific, not template-y."
    },
    tone: {
      type: "string",
      enum: ["professional", "friendly", "apologetic", "urgent"],
      default: "professional",
      description: "Tone of the email."
    },
    idempotency_key: {
      type: "string",
      description: "UUID for this draft. Use the same UUID if retrying to avoid duplicates."
    }
  }
}

“Efter”-versionerne leder LLM’en langt mere effektivt. De virker omfattende, men er indsatsen værd.

Konklusionen

Værktøjsdesign til LLM’er er en selvstændig disciplin. Principperne er ikke intuitive; de kræver, at du betragter LLM’en som brugeren og designer grænsefladen derefter.

De mønstre, der betyder noget:

  • Navne med handlingsverber.
  • Fyldestgørende beskrivelser, der forklarer hvad, hvornår og hvornår ikke.
  • Beskrivelser af hver parameter med eksempler og begrænsninger.
  • Strukturerede fejlmeddelelser med konkrete handlingsanvisninger.
  • Outputformater, der leder frem mod den næste handling.
  • Ét værktøj pr. koncept.
  • Fornuftige standardværdier.
  • En detaljeringsgrad, der understøtter sammensætning.
  • Eksplicit idempotens.
  • Dokumenterede forudsætninger og følgevirkninger.
  • Eksempler for komplekse værktøjer.
  • Interne detaljer holdes skjult.
  • Test med rigtige LLM’er.

De fleste MCP-servere fejler ikke, fordi protokollen er vanskelig, men fordi værktøjerne ikke blev designet med LLM’en for øje. Gør værktøjsdesignet rigtigt, og serveren bliver effektiv; gør det forkert, og din avancerede backend går til spilde.

Behandl LLM’en som brugeren. Design derefter. Investeringen betaler sig mange gange igen gennem den måde, værktøjerne faktisk bliver brugt på.

Læs næste

Fortsæt ad den samme læsevej med de næste praktiske artikler.