An MCP server can expose three things, and tools are only one of them. Resources are reference data the assistant loads into context, a policy document, a dataset, a config. Prompts are reusable, parameterized instructions the user invokes by name. On DenkOps you declare all three in the same defineMcp call, and the connector serves them from one /mcp endpoint.
What is a resource?
A resource is something the client reads, not something the model calls with arguments. It has a stable uri the client lists and fetches, an optional description and mimeType, and a read function that returns the contents as { text } (or { blob } for binary data):
uri: the identifier, likedocs://support/returns-policy.read(): returns the current contents each time it is fetched.
Because reading has no side effects and no arguments, clients can show resources to users directly, attach them to a conversation, and refresh them cheaply. A resource is a noun.
What is a prompt?
A prompt is a recipe with blanks. It has a description, an optional input schema for the blanks, and a build function that turns the arguments into messages, each { role: "user" | "assistant", content }. Clients surface prompts as named commands the user picks, so your best "here is exactly how to ask for this" phrasing ships with the server instead of living in a wiki.
What does all three together look like?
A small support kit, complete and deployable:
import { z } from "zod";
import { defineMcp } from "@denkopsai/sdk/mcp";
const POLICY = `Refunds within 30 days of delivery.
Store credit up to 90 days. No returns on custom items.`;
export default defineMcp({
name: "support-kit",
version: "1.0.0",
tools: {
lookup_order: {
description: "Fetch the status of one order by id.",
input: z.object({ id: z.string() }),
handler: ({ id }) => ({ id, status: "shipped" }),
},
},
resources: {
returns_policy: {
uri: "docs://support/returns-policy",
description: "The current returns policy.",
mimeType: "text/markdown",
read: () => ({ text: POLICY }),
},
},
prompts: {
refund_reply: {
description: "Draft a reply to a refund request.",
input: z.object({ orderId: z.string() }),
build: ({ orderId }) => [
{
role: "user",
content: `Look up order ${orderId} with lookup_order, check it against the returns policy resource, and draft a short, friendly reply.`,
},
],
},
},
});Note how the three reference each other: the prompt tells the assistant to use the tool and the resource. That composition is the payoff of not forcing everything into tools.
When do I use a tool, a resource, or a prompt?
- Tool: the model decides to act. Anything with arguments, side effects, or a computed answer.
lookup_ordermust be a tool, and its inputs deserve a real schema. - Resource: stable reference data with no arguments. Serving the policy as a resource beats a
get_policytool: no tool-call round trip, and the user can read the exact text the model saw. - Prompt: the human decides to start a workflow. If you keep pasting the same instructions into a chat, that text is a prompt definition waiting to happen.
Rule of thumb: verbs are tools, nouns are resources, recipes are prompts.
Deployment is unchanged: this file is a complete server, and one deploy puts it at a live HTTPS URL. Every MCP client that supports remote servers discovers resources and prompts through the same listing calls it uses for tools, so there is nothing extra to configure.