Model Context Protocol · API design · AI agents

MCP Tools vs. Resources vs. Prompts: A Practical Decision Guide

Choose the right MCP primitive for actions, application-selected context, and user-invoked workflows, then design each contract safely.

Published
Updated
Code examples
Illustrative
Reading time
5 minutes

Use an MCP tool for an operation the model may call, a resource for context the application reads by URI, and a prompt for a reusable workflow the user intentionally invokes. The simplest reliable choice follows who controls activation: model, application, or user.

The distinction comes from the MCP server model in specification version 2025-11-25: tools are model-controlled, resources are application-controlled, and prompts are user-controlled (MCP server overview). A host may present these through different interfaces, but collapsing them all into tools weakens predictability and policy.

Use this decision table

QuestionToolResourcePrompt
Who normally activates it?Model proposes a callApplication chooses contextUser selects a template
Primary outputOperation resultText or binary contentModel messages
Addressed byTool nameURI or URI templatePrompt name and arguments
May have side effects?Yes, with controlsReading should notNo direct server-side effect
Typical examplecreate_refund_previewrunbook://payments/refunds/investigate-refund

Ask three questions in order:

  1. Does the server need to perform an operation at model discretion? Choose a tool.
  2. Is the server publishing identifiable context for the host to read? Choose a resource.
  3. Is it a reusable conversation entry point that a person explicitly selects? Choose a prompt.

If none apply, use an ordinary API, configuration value, or application function. MCP is not required for every boundary.

Worked example: one refund workflow, three primitives

An accounts application needs a refund runbook, a safe preview operation, and an analyst-started investigation flow. These are related but should not be one generic refund tool.

Resource: publish the runbook

{
  "uri": "runbook://billing/refunds/current",
  "name": "Current refund runbook",
  "description": "Approved operational policy for refund review.",
  "mimeType": "text/markdown"
}

The application calls resources/read when the analyst opens the refund workflow or when local policy chooses to add it to context. The resource is identifiable and cacheable, and its URI communicates that it is the current view. For reproducibility, also publish an immutable version such as runbook://billing/refunds/revision/18.

Resources can be fixed or parameterized through templates. They may contain text or base64-encoded binary content. Servers can advertise subscriptions and list-change notifications; clients must negotiate those capabilities before using them (MCP resources specification).

Do not require the model to call get_refund_runbook merely to obtain stable context the application already knows it needs. That introduces another model decision and failure point.

Tool: calculate a bounded preview

{
  "name": "create_refund_preview",
  "title": "Create refund preview",
  "description": "Calculates a non-binding refund preview for one invoice.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "invoiceId": { "type": "string", "minLength": 1 },
      "reasonCode": {
        "type": "string",
        "enum": ["duplicate", "service_failure", "billing_error"]
      }
    },
    "required": ["invoiceId", "reasonCode"],
    "additionalProperties": false
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "previewId": { "type": "string" },
      "amountMinor": { "type": "integer", "minimum": 0 },
      "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
    },
    "required": ["previewId", "amountMinor", "currency"]
  }
}

The model may propose this operation. The server still validates arguments, derives the principal and tenant from authentication, authorizes the invoice, and returns structured output. The preview does not move money. Submission belongs in a separate tool requiring approval and an idempotency key.

Tools support annotations that describe properties such as read-only or destructive behavior, but clients must treat annotations as untrusted unless the server is trusted. An annotation helps presentation; it is not authorization (MCP tools specification).

Prompt: start a consistent investigation

{
  "name": "investigate_refund",
  "title": "Investigate a refund request",
  "description": "Starts a cited investigation using an invoice ID.",
  "arguments": [
    {
      "name": "invoiceId",
      "description": "Invoice to investigate",
      "required": true
    }
  ]
}

After the user selects it, prompts/get might return messages that instruct the model to read the versioned runbook, inspect the invoice using allowed capabilities, cite policy sections, and produce a preview rather than submit a refund.

Prompts are discoverable templates, not hidden system instructions. They are intended to be explicitly selected by the user. Validate prompt arguments and treat generated messages as model context, not as a bypass around host or tool policy (MCP prompts specification).

Avoid the generic-tool trap

These designs are risky:

http_request(method, url, headers, body)
database_query(sql)
filesystem(path, operation, content)

They expose implementation power instead of business outcomes, expand SSRF and injection risk, complicate authorization, and make model selection harder. Prefer narrow contracts such as get_invoice, create_refund_preview, and submit_approved_refund.

A tool should have one reviewable effect boundary. A resource should have a stable identity and content type. A prompt should define a useful user-invoked workflow without silently executing server effects.

Model data freshness explicitly

The primitives have different change mechanisms:

  • tools can advertise list changes when definitions change;
  • resources can advertise list changes and per-resource subscriptions;
  • prompts can advertise list changes.

These are negotiated optional capabilities. A client should not expect notifications from a server that did not advertise them, and should still recover through explicit list or read operations after reconnection.

For a mutable resource, include version information in content or metadata so downstream citations can say which revision was used. Prefer immutable URIs when an audit must reproduce the exact material. Do not place rapidly changing operational state in a prompt template if it should be fetched as a resource or tool result.

Separate discovery from authority

Listing a tool, resource, or prompt reveals a contract; it does not establish permission to use every object behind it.

For tools, authorize every call. For resources, authorize every list, template resolution, read, and subscription. For prompts, authorize listing and retrieval when templates expose restricted workflows or content. Filter discovery results by principal when necessary so names and descriptions do not leak sensitive capabilities.

Also treat all three surfaces as injection inputs:

  • tool descriptions can manipulate model behavior;
  • resource text can contain hostile instructions;
  • prompt templates can intentionally or accidentally override user goals.

Pin trusted servers, label provenance, keep data separate from instructions where possible, and never allow remote content to override local security policy.

When to combine primitives

The refund flow correctly uses all three because each has a distinct owner:

user selects investigation prompt
        ↓
application reads approved runbook resource
        ↓
model proposes bounded preview tool
        ↓
user reviews a separate commit action

Combining primitives is healthy when the transitions are visible. It is unhealthy when a prompt silently asks the model to call a destructive tool or when reading a resource triggers a write.

Review checklist by primitive

For a tool:

  • Is the operation outcome-specific and bounded?
  • Are input and output schemas strict?
  • Are authorization, approval, timeout, and idempotency defined?
  • Can the result safely enter model context?

For a resource:

  • Does it have a stable, meaningful URI and MIME type?
  • Is mutability or version identity explicit?
  • Are list, read, template, and subscription access authorized?
  • Is binary size bounded and content treated as untrusted?

For a prompt:

  • Is it clearly user-invoked?
  • Are arguments validated and safely interpolated?
  • Are retrieved messages understandable before execution?
  • Does it preserve host policy and approval boundaries?

Build the MCP server foundation before adding all three surfaces, and use the client guide to handle discovery defensively.