Skip to content

mcp-preferred-patterns

Common MCP server mistakes with detection commands and fixes — front matter parsing, caching failures, protocol violations

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

Common MCP server mistakes with detection commands and fixes — front matter parsing, caching failures, protocol violations

Agent definition

mcp-preferred-patterns.md
description: Common MCP server mistakes with detection commands and fixes — front matter parsing, caching failures, protocol violations

MCP Server Patterns Guide

> **Scope**: MCP documentation server patterns in TypeScript/Node.js. SDK 0.5.0+, Node.js 18+.

Pattern Catalog

Wrap Front Matter Parsing in Try/Catch

Parse defensively. A single malformed file must never crash the server.

function parseFrontMatter(content: string): { metadata: Partial<DocMetadata>; error?: string } {
  const parts = content.split('---');
  if (parts.length < 3) {
    return { metadata: {}, error: 'No front matter delimiters found' };
  }
  try {
    const metadata = yaml.parse(parts[1]) as DocMetadata;
    return { metadata: metadata ?? {} };
  } catch (err) {
    // Log warning, return empty metadata — do not crash
    return { metadata: {}, error: `YAML parse failed: ${(err as Error).message}` };
  }
}

// In indexing loop:
const { metadata, error } = parseFrontMatter(content);
if (error) {
  console.warn(`[warn] front matter: ${filePath}: ${error}`);
  // Continue indexing remaining files
}

**Why**: One bad YAML file crashes `yaml.parse()`, exiting the server and making 999 other docs inaccessible.

**Detection**:

# Find YAML parse calls without try/catch
grep -rn 'yaml\.parse\|yaml\.load\|toml\.parse' --include="*.ts" src/
# Check if they're wrapped in try/catch
grep -B5 'yaml\.parse\|yaml\.load' --include="*.ts" -rn src/

**Version note**: `js-yaml` >= 4.0 uses `yaml.load()` not `yaml.safeLoad()`. The `safeLoad` method was removed in 4.0.

---

Strip Hugo Shortcodes Before Sending Content

Remove `{{< >}}` and `{{% %}}` before returning to clients. LLMs interpret them as noise.

function stripHugoShortcodes(content: string): string {
  return content
    // Remove block shortcodes: {{< name >}}...{{< /name >}}
    .replace(/\{\{<\s*\/?\s*\w+[^>]*>\}\}/g, '')
    // Remove percent shortcodes: {{% name %}}
    .replace(/\{\{%[^%]*%\}\}/g, '')
    // Remove inline shortcodes with content
    .replace(/\{\{<\s*\w+[^>]*>\}\}[\s\S]*?\{\{<\s*\/\w+\s*>\}\}/g, '')
    // Clean up empty lines left by removal
    .replace(/\n{3,}/g, '\n\n');
}

**Why**: Shortcodes are not markdown. Raw shortcodes cause misinterpretation or hallucinated explanations.

**Detection**:

# Find files likely to have shortcodes
grep -rn '{{[<%]' --include="*.md" content/ | head -20
# Check if server strips them
grep -rn 'shortcode\|{{%\|{{<' --include="*.ts" src/

---

Declare All Implemented Capabilities

Register `resources` and `tools` in capabilities during init. Omit unused keys.

this.server = new Server(
  { name: 'local-docs', version: '1.0.0' },
  {
    capabilities: {
      resources: {},        // Enables resources/list and resources/read
      tools: {},            // Enables tools/list and tools/call
      // Only add these if you implement them:
      // prompts: {},
      // logging: {},
    },
  }
);

**Why**: Clients negotiate during `initialize`. Missing `capabilities.resources` = docs never discovered. Missing `capabilities.tools` = invisible tools.

**Detection**:

grep -rn 'capabilities' --include="*.ts" src/
grep -rn 'new Server(' --include="*.ts" src/ -A5 | grep -v 'capabilities'

**Version note**: SDK 0.6.0+ allows omitting empty `{}` for unimplemented capabilities — they default to absent.

---

Validate URI Paths Against Traversal

Resolve and verify containment within docs root. `path.join` normalizes but does NOT prevent traversal.

function safeUriToPath(docsRoot: string, uri: string): string {
  if (!uri.startsWith('docs://')) {
    throw new McpError(ErrorCode.InvalidParams, `Invalid URI scheme: ${uri}`);
  }
  const relative = uri.slice('docs://'.length);
  const resolved = path.resolve(docsRoot, relative);
  // Enforce that resolved path stays within docsRoot
  if (!resolved.startsWith(path.resolve(docsRoot) + path.sep) &&
      resolved !== path.resolve(docsRoot)) {
    throw new McpError(ErrorCode.InvalidParams, 'Path traversal not allowed');
  }
  return resolved;
}

**Why**: `path.join('/docs', '../etc/passwd')` = `/etc/passwd`. Clients can read any accessible file without validation.

**Detection**:

grep -rn 'uriToPath\|path\.join.*params\.uri\|path\.resolve.*params' --include="*.ts" src/

---

Filter Draft Documents From Resource Lists

Exclude `draft: true` from resource lists.

const resources = Array.from(this.docsIndex.values())
  .filter((doc) => !doc.metadata.draft)
  .map((doc) => ({
    uri: doc.uri,
    name: doc.metadata.title ?? path.basename(doc.path, '.md'),
    description: doc.metadata.description,
    mimeType: 'text/markdown',
  }));

**Why**: Drafts are unpublished. Exposing them lets Claude cite incomplete content as authoritative.

**Detection**:

# Check if draft filtering exists in list handler
grep -rn 'draft\|isDraft' --include="*.ts" src/
# Check Hugo content for draft: true documents
grep -rn '^draft: true\|^draft: "true"' --include="*.md" content/

---

Error-Fix Mappings

| Error Message | Root Cause | Fix | |---------------|------------|-----| | `YAMLException: unexpected end of the stream` | Unclosed YAML string in front matter | Wrap in try/catch, skip file with warning | | `TypeError: Cannot read properties of null (reading 'title')` | `yaml.parse()` returned null for empty front matter | Check `metadata ?? {}` after parse | | `McpError: Resource not found` at client | URI uses backslash on Windows (`docs://foo\bar`) | Normalize with `replace(/\\/g, '/')` in `pathToUri` | | Server exits with code 1 immediately | Uncaught promise rejection in `indexDocs()` | Add `.catch()` to indexing promise or wrap in try/catch | | Client sees 0 resources | `capabilities.resources` not declared | Add `resources: {}` to server capabilities | | `tools/list` returns empty array | `capabilities.tools` not

Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked