.cursor/skills/implementing-mcp-resources/SKILL.md
Implement new MCP resources and resource templates in the deno-mcp-template project. Provides the exact file structure, type signatures, registration steps, and patterns for static resources, KV-backed resources, resource templates with URI patterns, and subscription-based updates. Use when adding a new resource, creating MCP resources, or asking how resources work in this project.
npx skillsauth add phughesmcr/deno-mcp-template implementing-mcp-resourcesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Task Progress:
- [ ] Step 1: Create resource file in src/mcp/resources/
- [ ] Step 2: Define name, URI/template, config, and readCallback
- [ ] Step 3: Export as default ResourcePlugin or ResourceTemplatePlugin
- [ ] Step 4: Register in src/mcp/resources/mod.ts
- [ ] Step 5: (If KV-backed) Add URI-to-key mapping in kvKeys.ts
- [ ] Step 6: Run `deno task ci` to verify
| Type | Use Case | URI | Plugin Type |
|------|----------|-----|-------------|
| Static resource | Fixed content at a known URI | "hello://world" | ResourcePlugin |
| KV-backed resource | Persistent, mutable state | "counter://value" | ResourcePlugin |
| Resource template | Dynamic URI with variables | "greetings://{name}" | ResourceTemplatePlugin |
Create a new file in src/mcp/resources/.
import type { ResourceMetadata } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
import type { ResourcePlugin } from "$/shared/types.ts";
const name = "myResource";
const uri = "my-scheme://my-path";
const config: ResourceMetadata = {
description: "What this resource provides",
mimeType: "text/plain", // or "application/json"
};
async function readCallback(): Promise<ReadResourceResult> {
return {
contents: [{
uri,
text: "Resource content here",
}],
};
}
const module: ResourcePlugin = {
type: "resource",
name,
uri,
config,
readCallback,
};
export default module;
For resources with variable URI segments like greetings://{name}:
import {
type CompleteResourceTemplateCallback,
type ResourceMetadata,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
import type { ResourceTemplatePlugin } from "$/shared/types.ts";
const name = "myTemplate";
const completeName: CompleteResourceTemplateCallback = (value) => {
const prefix = value.trim().toLowerCase();
return suggestions.filter((s) => s.toLowerCase().startsWith(prefix)).slice(0, 5);
};
const template = new ResourceTemplate(
"my-scheme://{variable}",
{
list: undefined, // optional: callback to list all instances
complete: { variable: completeName }, // optional: autocomplete per variable
},
);
const config: ResourceMetadata = {
mimeType: "text/plain",
};
async function readCallback(
uri: URL,
variables: Record<string, unknown>,
): Promise<ReadResourceResult> {
const variable = variables.variable as string;
return {
contents: [{
uri: uri.toString(),
text: `Content for ${variable}`,
}],
};
}
const module: ResourceTemplatePlugin = {
type: "template",
name,
template,
config,
readCallback,
};
export default module;
In src/mcp/resources/mod.ts:
import myResource from "./myResource.ts";resources array:export const resources: AnyResourcePlugin[] = [
// ... existing resources
myResource,
];
The server dispatches based on resource.type:
"resource" calls registerResource(name, uri, config, readCallback)"template" calls registerResource(name, template, config, readCallback)From src/shared/types.ts:
export type ResourcePlugin = {
type: "resource";
name: string;
uri: string;
config: ResourceMetadata;
readCallback: ReadResourceCallback;
};
export type ResourceTemplatePlugin = {
type: "template";
name: string;
template: ResourceTemplate;
config: ResourceMetadata;
readCallback: ReadResourceTemplateCallback;
};
export type AnyResourcePlugin = ResourcePlugin | ResourceTemplatePlugin;
For resources with persistent mutable state:
myStore.ts) with KV read/write functions:import { getKvStore } from "$/kv/mod.ts";
export const MY_KEY: Deno.KvKey = ["resource", "my-resource", "value"];
export async function getValue(): Promise<string> {
const kv = await getKvStore();
const entry = await kv.get<string>(MY_KEY);
return entry.value ?? "default";
}
export async function setValue(value: string): Promise<void> {
const kv = await getKvStore();
await kv.set(MY_KEY, value);
}
import { getValue } from "./myStore.ts";
async function readCallback(): Promise<ReadResourceResult> {
const value = await getValue();
return { contents: [{ uri, text: JSON.stringify({ value }) }] };
}
src/mcp/resources/kvKeys.ts:import { MY_URI } from "./myResource.ts";
import { MY_KEY } from "./myStore.ts";
export const RESOURCE_KV_KEYS: ReadonlyMap<string, Deno.KvKey> = new Map([
[COUNTER_URI, COUNTER_KEY],
[MY_URI, MY_KEY], // add your mapping
]);
This enables automatic subscription notifications — when a tool mutates the KV key, the subscription tracker detects the change and notifies subscribed clients.
When resourceSubscribe: true in mcpServerDefinition (src/mcp/serverDefinition.ts), resources get subscription support:
subscriptionTracker.subscribe() starts watching the mapped KV keyresourceUpdated notificationsThe subscription tracker (src/mcp/resources/subscriptionTracker.ts) handles ref-counting, KV watching, and notifier lifecycle automatically.
Both resource types return the same shape:
{
contents: [{
uri: string, // the resource URI
text: string, // text content (for text/plain or application/json)
// OR
blob: string, // base64-encoded binary content
}],
}
Use text for text/JSON, blob for binary data. Set mimeType in config accordingly.
tools
Implement new MCP tools in the deno-mcp-template project. Provides the exact file structure, type signatures, registration steps, and patterns for standard tools, sampling tools, form and URL elicitation, resource-backed tools, and notification tools. Use when adding a new tool, creating MCP tools, or asking how tools work in this project.
tools
Implement new MCP prompts in the deno-mcp-template project. Provides the exact file structure, type signatures, registration steps, and patterns for prompts with static arguments or dynamic completions. Use when adding a new prompt, creating MCP prompts, or asking how prompts work in this project.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------