MCP Prompt Templates and Dynamic Content
Complete guide to MCP prompt templates and dynamic content, covering prompt resource design, template variables, context injection, multi-step prompt chains, dynamic prompt generation from data sources, and building reusable prompt libraries.
MCP Prompt Templates and Dynamic Content
Overview
MCP prompts are pre-built instructions that the server offers to the client. They are not just static text — they can accept arguments, pull in dynamic data, chain together, and adapt to context. Think of them as reusable, parameterized prompt recipes that your MCP server publishes. I use MCP prompts to encapsulate complex workflows that would otherwise require the user to write the same detailed instructions every time — code review templates, data analysis workflows, migration plan generators. The prompt does the thinking about what to ask; the model does the thinking about the answer.
Prerequisites
- Node.js 16 or later
@modelcontextprotocol/sdkpackage installed- Understanding of MCP server basics (tools, resources, prompts)
- Familiarity with prompt engineering concepts
- A use case where reusable, parameterized prompts would save time
Understanding MCP Prompts
MCP prompts have three key properties: they have a name, they accept arguments, and they return messages that the client can send to the model.
// A prompt returns an array of messages
// These messages are injected into the conversation
{
description: "Analyze a database table",
messages: [
{
role: "user",
content: {
type: "text",
text: "Analyze the 'users' table. Here is the schema: ..."
}
}
]
}
Prompts vs Tools vs Resources
Resources: "Here is data you can read" (passive, read-only)
Tools: "Here is an action you can take" (active, side effects)
Prompts: "Here is a workflow you can start" (interactive, templates)
Basic Prompt Server
var Server = require("@modelcontextprotocol/sdk/server/index.js").Server;
var StdioTransport = require("@modelcontextprotocol/sdk/server/stdio.js").StdioServerTransport;
var server = new Server(
{ name: "prompt-templates", version: "1.0.0" },
{ capabilities: { prompts: {} } }
);
// List available prompts
server.setRequestHandler("prompts/list", function() {
return {
prompts: [
{
name: "code-review",
description: "Review code for bugs, security issues, performance, and best practices",
arguments: [
{
name: "code",
description: "The code to review",
required: true
},
{
name: "language",
description: "Programming language (javascript, python, etc.)",
required: false
},
{
name: "focus",
description: "Review focus: security, performance, readability, or all",
required: false
}
]
},
{
name: "explain-error",
description: "Explain an error message and suggest fixes",
arguments: [
{
name: "error",
description: "The error message or stack trace",
required: true
},
{
name: "context",
description: "What you were doing when the error occurred",
required: false
}
]
},
{
name: "write-tests",
description: "Generate test cases for a function or module",
arguments: [
{
name: "code",
description: "The code to write tests for",
required: true
},
{
name: "framework",
description: "Test framework: jest, mocha, or tap",
required: false
}
]
}
]
};
});
// Get a specific prompt with arguments filled in
server.setRequestHandler("prompts/get", function(request) {
var name = request.params.name;
var args = request.params.arguments || {};
switch (name) {
case "code-review":
return getCodeReviewPrompt(args);
case "explain-error":
return getExplainErrorPrompt(args);
case "write-tests":
return getWriteTestsPrompt(args);
default:
throw new Error("Unknown prompt: " + name);
}
});
function getCodeReviewPrompt(args) {
var language = args.language || "javascript";
var focus = args.focus || "all";
var focusInstructions = {
security: "Focus exclusively on security vulnerabilities: injection attacks, authentication flaws, data exposure, insecure dependencies, and OWASP Top 10 issues.",
performance: "Focus exclusively on performance: algorithmic complexity, memory leaks, unnecessary allocations, N+1 queries, missing caching, and blocking operations.",
readability: "Focus exclusively on readability: naming conventions, function length, code organization, comments quality, and maintainability.",
all: "Review for security vulnerabilities, performance issues, readability, and adherence to best practices."
};
var reviewFocus = focusInstructions[focus] || focusInstructions.all;
return {
description: "Code review for " + language + " (" + focus + " focus)",
messages: [
{
role: "user",
content: {
type: "text",
text: "Please review the following " + language + " code.\n\n"
+ reviewFocus + "\n\n"
+ "For each issue found, provide:\n"
+ "1. The specific line or section\n"
+ "2. What the issue is\n"
+ "3. Why it matters\n"
+ "4. How to fix it with a code example\n\n"
+ "Rate the overall code quality from 1-10.\n\n"
+ "```" + language + "\n"
+ args.code + "\n"
+ "```"
}
}
]
};
}
function getExplainErrorPrompt(args) {
var contextLine = args.context
? "\n\nContext: " + args.context
: "";
return {
description: "Error explanation and fix suggestions",
messages: [
{
role: "user",
content: {
type: "text",
text: "I encountered the following error:" + contextLine + "\n\n"
+ "```\n" + args.error + "\n```\n\n"
+ "Please:\n"
+ "1. Explain what this error means in plain language\n"
+ "2. Identify the most likely cause\n"
+ "3. Provide 2-3 possible fixes, starting with the most likely\n"
+ "4. Show code examples for each fix\n"
+ "5. Explain how to prevent this error in the future"
}
}
]
};
}
function getWriteTestsPrompt(args) {
var framework = args.framework || "jest";
return {
description: "Test generation using " + framework,
messages: [
{
role: "user",
content: {
type: "text",
text: "Write comprehensive tests for the following code using " + framework + ".\n\n"
+ "```javascript\n" + args.code + "\n```\n\n"
+ "Include:\n"
+ "- Happy path tests for all public functions\n"
+ "- Edge cases (null, undefined, empty, boundary values)\n"
+ "- Error handling tests\n"
+ "- Any mocking needed for external dependencies\n\n"
+ "Use descriptive test names that explain the expected behavior.\n"
+ "Use `var` and `function()` syntax (not const/let/arrow)."
}
}
]
};
}
var transport = new StdioTransport();
server.connect(transport).then(function() {
console.error("Prompt templates MCP server running");
});
Dynamic Content Injection
Prompts become powerful when they pull in live data from your project.
File-Aware Prompts
var fs = require("fs");
var path = require("path");
var PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
// Prompt that pulls in actual project files
function getRefactorPrompt(args) {
var filePath = path.join(PROJECT_DIR, args.file);
// Validate path is within project
if (!path.resolve(filePath).startsWith(path.resolve(PROJECT_DIR))) {
throw new Error("File must be within the project directory");
}
if (!fs.existsSync(filePath)) {
throw new Error("File not found: " + args.file);
}
var fileContent = fs.readFileSync(filePath, "utf8");
var ext = path.extname(filePath).slice(1);
// Also read related files if they exist
var relatedFiles = [];
var testFile = filePath.replace(".js", ".test.js");
if (fs.existsSync(testFile)) {
relatedFiles.push({
path: path.relative(PROJECT_DIR, testFile),
content: fs.readFileSync(testFile, "utf8")
});
}
var messages = [
{
role: "user",
content: {
type: "text",
text: "Refactor the following file: `" + args.file + "`\n\n"
+ (args.goal ? "Refactoring goal: " + args.goal + "\n\n" : "")
+ "Current implementation:\n"
+ "```" + ext + "\n" + fileContent + "\n```"
}
}
];
if (relatedFiles.length > 0) {
var relatedText = relatedFiles.map(function(rf) {
return "Related file `" + rf.path + "`:\n```" + ext + "\n" + rf.content + "\n```";
}).join("\n\n");
messages.push({
role: "user",
content: {
type: "text",
text: relatedText + "\n\nPlease update the test file to match any refactored interfaces."
}
});
}
return {
description: "Refactor " + args.file,
messages: messages
};
}
Database-Aware Prompts
var pg = require("pg");
var pool = new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 3 });
// Prompt that includes live database schema
function getDataAnalysisPrompt(args) {
var tableName = args.table;
if (!/^[a-zA-Z_]\w*$/.test(tableName)) {
throw new Error("Invalid table name");
}
return Promise.all([
// Get schema
pool.query(
"SELECT column_name, data_type, is_nullable " +
"FROM information_schema.columns " +
"WHERE table_schema = 'public' AND table_name = $1 " +
"ORDER BY ordinal_position",
[tableName]
),
// Get sample data
pool.query("SELECT * FROM public." + tableName + " LIMIT 5"),
// Get row count
pool.query("SELECT COUNT(*) as count FROM public." + tableName)
]).then(function(results) {
var schema = results[0].rows;
var samples = results[1].rows;
var count = results[2].rows[0].count;
var schemaText = schema.map(function(col) {
return " " + col.column_name + " " + col.data_type + (col.is_nullable === "NO" ? " NOT NULL" : "");
}).join("\n");
return {
description: "Data analysis for " + tableName + " (" + count + " rows)",
messages: [
{
role: "user",
content: {
type: "text",
text: "Analyze the `" + tableName + "` table (" + count + " total rows).\n\n"
+ "Schema:\n```\n" + schemaText + "\n```\n\n"
+ "Sample data:\n```json\n" + JSON.stringify(samples, null, 2) + "\n```\n\n"
+ (args.question
? "Specific question: " + args.question + "\n\n"
: "")
+ "Provide:\n"
+ "1. Summary of what this table contains\n"
+ "2. Data quality observations from the sample\n"
+ "3. Suggested SQL queries for common analyses\n"
+ "4. Any potential issues or optimizations"
}
}
]
};
});
}
Template Variable System
For complex prompts with many variables, build a template engine:
// Simple template engine for prompt text
function renderTemplate(template, variables) {
return template.replace(/\{\{(\w+)(?:\|([^}]+))?\}\}/g, function(match, name, defaultValue) {
if (variables[name] !== undefined && variables[name] !== null && variables[name] !== "") {
return variables[name];
}
if (defaultValue !== undefined) {
return defaultValue;
}
return match; // Leave unreplaced if no value and no default
});
}
// Template with conditionals
function renderConditional(template, variables) {
// Handle {{#if var}}...{{/if}} blocks
template = template.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, function(match, name, content) {
if (variables[name]) {
return renderTemplate(content, variables);
}
return "";
});
// Handle {{#each items}}...{{/each}} blocks
template = template.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, function(match, name, content) {
var items = variables[name];
if (!Array.isArray(items)) return "";
return items.map(function(item, index) {
var itemVars = Object.assign({}, variables, { item: item, index: index });
return renderTemplate(content, itemVars);
}).join("");
});
return renderTemplate(template, variables);
}
// Usage in prompt definitions
var PROMPT_TEMPLATES = {
"api-design": {
template: "Design a REST API for {{resource}}.\n\n"
+ "Requirements:\n"
+ "- Base URL: {{baseUrl|/api/v1}}\n"
+ "- Authentication: {{auth|Bearer token}}\n"
+ "{{#if existingEndpoints}}"
+ "\nExisting endpoints to be compatible with:\n{{existingEndpoints}}\n"
+ "{{/if}}"
+ "\nInclude:\n"
+ "1. All CRUD endpoints with request/response schemas\n"
+ "2. Pagination, filtering, and sorting\n"
+ "3. Error response format\n"
+ "4. Rate limiting headers\n"
+ "{{#if includeOpenApi}}5. OpenAPI 3.0 specification\n{{/if}}",
arguments: [
{ name: "resource", required: true, description: "The resource to design an API for (e.g., 'users', 'products')" },
{ name: "baseUrl", required: false, description: "Base URL path" },
{ name: "auth", required: false, description: "Authentication method" },
{ name: "existingEndpoints", required: false, description: "Existing API endpoints to maintain compatibility with" },
{ name: "includeOpenApi", required: false, description: "Include OpenAPI spec in the output" }
]
},
"migration-plan": {
template: "Create a database migration plan.\n\n"
+ "Current schema:\n```sql\n{{currentSchema}}\n```\n\n"
+ "Target changes:\n{{changes}}\n\n"
+ "Requirements:\n"
+ "- Zero-downtime migration\n"
+ "- Rollback plan for each step\n"
+ "- Data backfill strategy if needed\n"
+ "{{#if constraints}}\nConstraints: {{constraints}}\n{{/if}}"
+ "{{#if estimatedRows}}\nEstimated rows affected: {{estimatedRows}}\n{{/if}}",
arguments: [
{ name: "currentSchema", required: true, description: "Current database schema (SQL)" },
{ name: "changes", required: true, description: "What needs to change" },
{ name: "constraints", required: false, description: "Any constraints (downtime window, etc.)" },
{ name: "estimatedRows", required: false, description: "Estimated rows in affected tables" }
]
}
};
// Register all template-based prompts
server.setRequestHandler("prompts/list", function() {
var prompts = Object.keys(PROMPT_TEMPLATES).map(function(name) {
var tmpl = PROMPT_TEMPLATES[name];
return {
name: name,
description: tmpl.template.split("\n")[0].replace(/\{\{[^}]+\}\}/g, "..."),
arguments: tmpl.arguments
};
});
return { prompts: prompts };
});
server.setRequestHandler("prompts/get", function(request) {
var name = request.params.name;
var args = request.params.arguments || {};
var tmpl = PROMPT_TEMPLATES[name];
if (!tmpl) throw new Error("Unknown prompt: " + name);
// Check required arguments
tmpl.arguments.forEach(function(arg) {
if (arg.required && !args[arg.name]) {
throw new Error("Missing required argument: " + arg.name);
}
});
var text = renderConditional(tmpl.template, args);
return {
description: name + " prompt",
messages: [{ role: "user", content: { type: "text", text: text } }]
};
});
Multi-Step Prompt Chains
Some workflows require multiple prompts in sequence, where each step builds on the previous.
// Multi-step prompt chain for incident investigation
var INCIDENT_CHAIN = {
name: "investigate-incident",
description: "Step-by-step incident investigation workflow",
steps: [
{
name: "gather-context",
prompt: function(args) {
return "An incident has been reported.\n\n"
+ "Incident: {{description}}\n"
+ "Severity: {{severity|unknown}}\n"
+ "Reported at: {{reportedAt}}\n\n"
+ "First, let's gather context. Please analyze the following logs "
+ "and identify the timeline of events, affected systems, and potential root causes.\n\n"
+ "```\n{{logs}}\n```";
}
},
{
name: "identify-root-cause",
prompt: function(args) {
return "Based on the context gathered in the previous step, "
+ "identify the most likely root cause.\n\n"
+ "Consider:\n"
+ "1. What changed recently? (deployments, config changes, traffic patterns)\n"
+ "2. Are there correlated failures across services?\n"
+ "3. Is this a known failure mode?\n\n"
+ "{{#if metrics}}Additional metrics:\n{{metrics}}\n\n{{/if}}"
+ "Provide your top 3 hypotheses ranked by likelihood.";
}
},
{
name: "remediation-plan",
prompt: function(args) {
return "Based on the identified root cause, create a remediation plan.\n\n"
+ "Include:\n"
+ "1. Immediate mitigation (stop the bleeding)\n"
+ "2. Root cause fix (prevent recurrence)\n"
+ "3. Verification steps (confirm the fix works)\n"
+ "4. Communication template (stakeholder updates)\n"
+ "5. Post-incident tasks (monitoring, documentation)";
}
}
]
};
// Expose the chain as a prompt with a step argument
server.setRequestHandler("prompts/get", function(request) {
if (request.params.name === "investigate-incident") {
var args = request.params.arguments || {};
var step = parseInt(args.step || "1") - 1;
if (step < 0 || step >= INCIDENT_CHAIN.steps.length) {
throw new Error("Invalid step. Available steps: 1-" + INCIDENT_CHAIN.steps.length);
}
var stepDef = INCIDENT_CHAIN.steps[step];
var text = renderConditional(stepDef.prompt(args), args);
return {
description: "Incident investigation - Step " + (step + 1) + ": " + stepDef.name,
messages: [
{
role: "user",
content: {
type: "text",
text: "[Step " + (step + 1) + " of " + INCIDENT_CHAIN.steps.length + ": " + stepDef.name + "]\n\n" + text
}
}
]
};
}
});
Complete Working Example: Project Assistant Prompt Library
var Server = require("@modelcontextprotocol/sdk/server/index.js").Server;
var StdioTransport = require("@modelcontextprotocol/sdk/server/stdio.js").StdioServerTransport;
var fs = require("fs");
var path = require("path");
// ============================================================
// Project Assistant Prompt Library
// Reusable, context-aware prompts for common dev workflows
// ============================================================
var PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
var server = new Server(
{ name: "project-prompts", version: "1.0.0" },
{ capabilities: { prompts: {} } }
);
// Prompt definitions
var prompts = {
"code-review": {
description: "Thorough code review with actionable feedback",
arguments: [
{ name: "file", required: true, description: "File path relative to project root" },
{ name: "focus", required: false, description: "Focus area: security, performance, readability, or all" }
],
handler: function(args) {
var filePath = path.join(PROJECT_DIR, args.file);
if (!fs.existsSync(filePath)) throw new Error("File not found: " + args.file);
var code = fs.readFileSync(filePath, "utf8");
var ext = path.extname(args.file).slice(1);
var focus = args.focus || "all";
// Try to read package.json for project context
var projectContext = "";
var pkgPath = path.join(PROJECT_DIR, "package.json");
if (fs.existsSync(pkgPath)) {
var pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
projectContext = "\nProject: " + pkg.name + " (Node " + (pkg.engines && pkg.engines.node || "any") + ")\n"
+ "Dependencies: " + Object.keys(pkg.dependencies || {}).join(", ") + "\n";
}
return {
description: "Code review: " + args.file + " (" + focus + ")",
messages: [{
role: "user",
content: {
type: "text",
text: "Review this " + ext + " file: `" + args.file + "`\n"
+ projectContext
+ "\nFocus: " + focus + "\n\n"
+ "```" + ext + "\n" + code + "\n```\n\n"
+ "Provide specific, actionable feedback. For each issue:\n"
+ "- Quote the problematic code\n"
+ "- Explain the issue and its impact\n"
+ "- Show a corrected version\n\n"
+ "End with an overall quality score (1-10) and a summary."
}
}]
};
}
},
"debug-help": {
description: "Get help debugging an error with full project context",
arguments: [
{ name: "error", required: true, description: "Error message or stack trace" },
{ name: "file", required: false, description: "File where the error occurs" }
],
handler: function(args) {
var messages = [];
var contextText = "";
if (args.file) {
var filePath = path.join(PROJECT_DIR, args.file);
if (fs.existsSync(filePath)) {
contextText = "\n\nSource file `" + args.file + "`:\n```\n"
+ fs.readFileSync(filePath, "utf8") + "\n```";
}
}
// Look for relevant config files
var configFiles = [".env.example", "tsconfig.json", "package.json"];
var configs = configFiles.filter(function(f) {
return fs.existsSync(path.join(PROJECT_DIR, f));
}).map(function(f) {
return "`" + f + "`: " + fs.readFileSync(path.join(PROJECT_DIR, f), "utf8").substring(0, 500);
});
messages.push({
role: "user",
content: {
type: "text",
text: "I need help debugging this error:\n\n"
+ "```\n" + args.error + "\n```"
+ contextText
+ (configs.length > 0 ? "\n\nProject config:\n" + configs.join("\n\n") : "")
+ "\n\nPlease:\n"
+ "1. Explain the error in plain language\n"
+ "2. Identify the root cause\n"
+ "3. Provide a step-by-step fix\n"
+ "4. Show the corrected code"
}
});
return { description: "Debug: " + args.error.substring(0, 50), messages: messages };
}
},
"generate-api-docs": {
description: "Generate API documentation from route files",
arguments: [
{ name: "routeFile", required: true, description: "Express route file to document" },
{ name: "format", required: false, description: "Output format: markdown, openapi, or jsdoc" }
],
handler: function(args) {
var filePath = path.join(PROJECT_DIR, args.routeFile);
if (!fs.existsSync(filePath)) throw new Error("Route file not found");
var code = fs.readFileSync(filePath, "utf8");
var format = args.format || "markdown";
return {
description: "API docs for " + args.routeFile,
messages: [{
role: "user",
content: {
type: "text",
text: "Generate " + format + " API documentation for this Express route file:\n\n"
+ "```javascript\n" + code + "\n```\n\n"
+ "For each endpoint, document:\n"
+ "- HTTP method and path\n"
+ "- Description of what it does\n"
+ "- Request parameters, query params, and body schema\n"
+ "- Response format with example\n"
+ "- Error responses\n"
+ "- Authentication requirements"
}
}]
};
}
},
"write-commit-message": {
description: "Generate a commit message from staged changes",
arguments: [
{ name: "diff", required: true, description: "Git diff output of staged changes" },
{ name: "style", required: false, description: "Commit style: conventional, simple, or detailed" }
],
handler: function(args) {
var style = args.style || "conventional";
var styleGuide = {
conventional: "Use Conventional Commits format: type(scope): description\n"
+ "Types: feat, fix, docs, style, refactor, perf, test, chore\n"
+ "Keep the first line under 72 characters.",
simple: "Write a single-line commit message under 72 characters that describes what changed and why.",
detailed: "Write a commit message with:\n"
+ "- A short summary line (under 72 chars)\n"
+ "- A blank line\n"
+ "- A detailed body explaining what changed and why\n"
+ "- Bullet points for multiple changes"
};
return {
description: "Commit message (" + style + " style)",
messages: [{
role: "user",
content: {
type: "text",
text: "Generate a git commit message for these changes:\n\n"
+ "```diff\n" + args.diff + "\n```\n\n"
+ "Style: " + (styleGuide[style] || styleGuide.conventional) + "\n\n"
+ "Return ONLY the commit message, no explanation."
}
}]
};
}
},
"project-overview": {
description: "Generate a comprehensive overview of the project structure",
arguments: [],
handler: function() {
// Collect project structure
var structure = [];
function walk(dir, depth) {
if (depth > 3) return;
var entries;
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { return; }
entries.forEach(function(entry) {
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") return;
var rel = path.relative(PROJECT_DIR, path.join(dir, entry.name));
var indent = " ".repeat(depth);
if (entry.isDirectory()) {
structure.push(indent + rel + "/");
walk(path.join(dir, entry.name), depth + 1);
} else {
var size = fs.statSync(path.join(dir, entry.name)).size;
structure.push(indent + rel + " (" + formatSize(size) + ")");
}
});
}
walk(PROJECT_DIR, 0);
// Read key files
var keyFiles = {};
["package.json", "README.md", "app.js", "index.js"].forEach(function(f) {
var fp = path.join(PROJECT_DIR, f);
if (fs.existsSync(fp)) {
keyFiles[f] = fs.readFileSync(fp, "utf8").substring(0, 1000);
}
});
var contextParts = ["Project structure:\n```\n" + structure.join("\n") + "\n```"];
Object.keys(keyFiles).forEach(function(f) {
contextParts.push("`" + f + "`:\n```\n" + keyFiles[f] + "\n```");
});
return {
description: "Project overview",
messages: [{
role: "user",
content: {
type: "text",
text: "Provide a comprehensive overview of this project.\n\n"
+ contextParts.join("\n\n")
+ "\n\nCover:\n"
+ "1. What this project does\n"
+ "2. Technology stack\n"
+ "3. Key files and their purposes\n"
+ "4. How to set it up and run it\n"
+ "5. Architecture patterns used"
}
}]
};
}
}
};
function formatSize(bytes) {
if (bytes < 1024) return bytes + "B";
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + "KB";
return (bytes / 1048576).toFixed(1) + "MB";
}
// Register handlers
server.setRequestHandler("prompts/list", function() {
return {
prompts: Object.keys(prompts).map(function(name) {
return {
name: name,
description: prompts[name].description,
arguments: prompts[name].arguments
};
})
};
});
server.setRequestHandler("prompts/get", function(request) {
var name = request.params.name;
var args = request.params.arguments || {};
if (!prompts[name]) throw new Error("Unknown prompt: " + name);
// Validate required arguments
(prompts[name].arguments || []).forEach(function(arg) {
if (arg.required && !args[arg.name]) {
throw new Error("Missing required argument: " + arg.name);
}
});
return prompts[name].handler(args);
});
var transport = new StdioTransport();
server.connect(transport).then(function() {
console.error("Project prompts MCP server running");
console.error(" Project: " + PROJECT_DIR);
console.error(" Available prompts: " + Object.keys(prompts).join(", "));
});
Common Issues & Troubleshooting
Prompt Returns Empty Messages Array
The messages array must contain at least one message:
// Wrong: empty messages
return { description: "test", messages: [] };
// Right: always include at least one message
return {
description: "test",
messages: [{ role: "user", content: { type: "text", text: "..." } }]
};
Dynamic File Content Contains Special Characters
File content with backticks, template literals, or other markdown-conflicting characters can break prompt formatting:
// Escape backticks in code blocks
function escapeForCodeBlock(content) {
// Replace triple backticks with escaped version
return content.replace(/```/g, "\\`\\`\\`");
}
// Or use different delimiters
var text = "Code:\n<code>\n" + content + "\n</code>";
Prompt Arguments Not Validated
Always validate arguments before using them in file paths or queries:
function validateFilePath(relativePath) {
var fullPath = path.join(PROJECT_DIR, relativePath);
var resolved = path.resolve(fullPath);
if (!resolved.startsWith(path.resolve(PROJECT_DIR))) {
throw new Error("Path traversal detected");
}
if (!fs.existsSync(resolved)) {
throw new Error("File not found: " + relativePath);
}
return resolved;
}
Prompt Template Rendering Fails Silently
Unreplaced template variables stay in the output. Add a warning:
function renderWithWarnings(template, variables) {
var result = renderTemplate(template, variables);
var unreplaced = result.match(/\{\{[^}]+\}\}/g);
if (unreplaced) {
console.error("Warning: unreplaced template variables: " + unreplaced.join(", "));
}
return result;
}
Best Practices
- Keep prompts focused on a single workflow — A prompt that tries to review code, write tests, and generate docs all at once is too complex. Split into separate prompts.
- Include project context automatically — Read package.json, config files, and directory structure to give the model context without requiring the user to provide it manually.
- Use arguments for customization, not for essential content — Required arguments should be the minimum needed. Optional arguments customize behavior (format, focus, style).
- Validate file paths against the project directory — Never allow prompts to read files outside the project root. Path traversal is a real risk.
- Return descriptive descriptions — The
descriptionfield in the response should reflect the specific prompt instance, not just the generic template name. - Support multiple output formats — Where applicable, let users choose between markdown, JSON, YAML, or other formats through a
formatargument. - Document your prompts with examples — Include example argument values in the argument descriptions so users know what to provide.
- Test prompts with real project data — A prompt that works with sample code may fail with real files containing edge cases, large content, or unexpected formats.