Skip to content

Latest commit

 

History

History
233 lines (168 loc) · 10.7 KB

File metadata and controls

233 lines (168 loc) · 10.7 KB

Agent Coding

Overview

Coding standards and architectural patterns for the PatternFly MCP project. This document emphasizes maintainability, performance, and pragmatic TypeScript usage.

For Agents

Processing Priority

High - This document should be processed when working with source code or implementing features.

Related Guidelines

See the Guidelines Index for all guidelines.

1. TypeScript Standards

Adhere to the TypeScript coding conventions established for the project.

Core Principles

  • Pragmatic Over Perfection: Focus on code and functionality. Types are for consumer ergonomics and quick sanity checks — not a blocker for implementation speed.
  • Generics should be reserved: Use for public exported functions/typings. If they reduce readability, prefer concrete typings.
  • Prefer unknown over any: unknown is the default at boundaries; add runtime guards and narrow. any is still acceptable in testing or deserialization (IPC).
  • Prefer inference over explicit returns: Let inference work unless the function/type is part of the public surface.
  • Style Preference (Nullish Coalescing): Align to the codebase's established style. Prefer logical OR (||) over lazy application of the nullish coalescing operator (??). Use nullish coalescing when an active distinction for null/undefined (e.g., distinguishing from 0, "", or false) is logically required.
  • Avoid Over-Engineering: Do not use overabundant type guards or complex nested ternaries. Prefer clear, readable logic over opaque TypeScript patterns.

Strict ESM Enforcement

The project is strictly ESM. Agents MUST follow these rules:

  • Exports:
    • Internal Source Code (TypeScript): Favor named exports (e.g., export { foo }).
    • External Tool Plugins (JavaScript): MUST use export default for the tool definition.
  • Explicit Extensions:
    • Internal Source Code (TypeScript): Use extension-less imports for local modules (e.g., import { foo } from './foo').
    • External Tool Plugins (JavaScript): All relative imports MUST include explicit file extensions (e.g., import { foo } from './foo.js') as they are loaded by the Node.js ESM runtime.
  • No CommonJS: Do not use require(), module.exports, or __dirname.

When to Bypass TypeScript (Localized Opt-out)

Specific modules allow bypassing strict typing to maintain momentum:

  • Internal tool composition (src/server.tools.ts): Use localized casts for inferred unions that don't narrow. Add a short comment explaining intent.
  • Schema conversion (src/server.schema.ts): Returning z.ZodTypeAny is fine. Avoid deep type plumbing.
  • Tools Host IPC (src/server.toolsHost.ts): any for deserialized payloads is acceptable. Runtime checks and try/catch are the safety net.
  • Test fixtures and E2E clients: Use // @ts-ignore or as any where tests exercise built outputs or where typings aren’t the point under test.

2. Architectural Patterns

The project follows a plugin-based architecture focused on stability and extensibility.

2.1 Tool and Resource Authoring (The "Creator" Pattern)

All tools and resources MUST follow the Creator Pattern for dependency injection and testability.

  • Structure: Creator functions accept an optional options parameter (defaults to getOptions()) and return a tool/resource tuple.
  • Options Hybrid Approach: Environment-dependent helpers should accept an optional options parameter that defaults to getOptions(). This allows for explicit dependency injection in tests while maintaining ergonomics via AsyncLocalStorage in production. Pure transforms should remain option-agnostic.
  • Internal Tools: (options = getOptions()): McpTool -> Returns [name, schema, handler].
  • Internal Resources: (options = getOptions()): McpResource -> Returns [name, uri, config, handler].
  • External Tool Plugins: Authored using the createMcpTool helper with an object configuration, exported as default.
  • Testing: Creators allow easy mocking: const tool = usePatternFlyDocsTool(mockOptions).

2.2 Module Organization and Exports

  • File Naming:
    • Internal: lowerCamelCase with dot notation (e.g., server.http.ts, tool.docs.ts).
    • Prefixes: server.* (core), tool.* (built-in tools), resource.* (resources), options.* (config).
    • External/Examples: lowerCamelCase with descriptive prefixes (e.g., toolPluginHello.js).
  • Export Patterns:
    • Internal (TS): Use named exports grouped at the end of the file.
    • External (JS): Use default export for the primary tool definition.
  • Concurrency: Use processDocsFunction for multi-file loading to leverage the promiseQueue (sliding window pattern). Respect maxDocsToLoad.

2.3 External Tool Plugin Scaffold

External tool plugins should follow this basic structure:

import { createMcpTool } from '@patternfly/patternfly-mcp';

export default createMcpTool({
  name: 'myTool',
  description: 'Tool description',
  inputSchema: {
    type: 'object',
    properties: {
      param1: { type: 'string', description: 'Parameter description' }
    },
    required: ['param1']
  },
  async handler({ param1 }) {
    return {
      content: [{ type: 'text', text: `Result: ${param1}` }]
    };
  }
});

3. Common Patterns

3.1 Memoization

Expensive operations (network, I/O, schema processing) should be memoized by assigning a .memo property to the function. This allows easier testing of the original function.

  • Usage: const result = await getComponentSchema.memo('Button');
  • Pattern:
const expensiveFn = async (arg: string) => { /* ... */ };

expensiveFn.memo = memo(expensiveFn, { 
  cacheLimit: 10, 
  keyHash: (args) => args[0] 
});
  • Note: Use cacheErrors: false if normalization should retry on subsequent attempts.

3.2 Context Management

Use AsyncLocalStorage via helper functions to access session and global options without parameter drilling.

  • Tool Pattern:
const myTool = (options = getOptions()): McpTool => {
  const session = getSessionOptions();
  return [name, schema, async (args) => { /* use options/session */ }];
};
  • Execution: Context is automatically preserved across async boundaries. Use runWithSession or runWithOptions only when explicit overrides are required.

3.3 Error Handling and "Suggestive Failure"

Always use McpError with appropriate ErrorCode for user-facing failures. When a resource is not found, provide suggestions.

Note on Error Codes: To avoid drift, we do not maintain a local list of MCP SDK ErrorCode values. Agents MUST analyze the current version of the @modelcontextprotocol/sdk package to identify correct codes. Avoid listing package-specific resources we do not control in agent documentation due to maintenance concerns.

const { exactMatches, searchResults } = searchComponents.memo(name);

if (exactMatches.length === 0) {
  const suggestions = searchResults.map(r => r.item).slice(0, 3);

  throw new McpError(
    ErrorCode.InvalidParams,
    `"${name}" not found. Did you mean ${suggestions.map(s => `"${s}"`).join(', ')}?`
  );
}

3.4 Core Utilities (Helpers, Schema, Guards)

Centralized utilities in server.helpers.ts, server.schema.ts, and server.getResources.ts should be favored over re-implementation.

  • Helpers: Use stringJoin.newline(), freezeObject(), timeoutFunction(), and mergeObjects().
  • Schemas: Use normalizeInputSchema(schema) to convert JSON Schema or Zod shapes into valid Zod instances.
  • Type Guards: Use isPlainObject(), isZodSchema(), and isErrorLike() for runtime narrowing.
  • Zod Detection: Robustly detect Zod schemas by checking for internal brands: _def for Zod v3 and _zod for Zod v4. Avoid relying solely on parse() or safeParse() methods.
  • Immutability: Options and Session objects are frozen; use structuredClone() before modification.

3.5 Validation and Sanitization

Validate input at boundaries (handlers) and sanitize objects before merging.

const handler = async (args: unknown) => {
  if (!isPlainObject(args)) {
    throw new McpError(ErrorCode.InvalidParams, 'Object required');
  }

  const { name } = args;

  if (typeof name !== 'string' || !name.trim()) {
    throw new McpError(ErrorCode.InvalidParams, 'Name string required');
  }

  return process(name.trim());
};

3.6 Async Patterns (Return Await)

Use return await ONLY when catching/translating errors in the current layer or when a finally block must execute before returning. Otherwise, return the promise directly.

3.7 Concurrency Patterns (Sliding Window)

The project favors a "sliding window" pattern for promise queues (see src/server.getResources.ts) over strict batching. This maintains a constant number of active requests rather than waiting for an entire batch to complete.

4. JSDoc Documentation Standards

While the codebase emphasizes pragmatism, public APIs require comprehensive JSDoc for consumer ergonomics.

4.1 Required Tags for Public APIs

  • @param, @returns, @throws: Standard documentation for function signature.
  • @property: Document properties for interfaces or classes.
  • @alias: Used for stable aliased typings exposed to consumers.
  • @template: Document generic type parameters.
  • @example: Provide usage examples for complex functions.
  • @note: Important implementation details or gotchas.

4.2 JSDoc Format Examples

/**
 * Fetches documentation for a PatternFly component.
 * 
 * @param name - Component name (e.g., 'Button')
 * @param options - Configuration
 * @param options.maxDocs - Max items to load
 * @returns Tool tuple [name, schema, handler]
 * @throws {McpError} If component not found
 * @example
 * const tool = usePatternFlyDocsTool();
 * const res = await tool[2]({ name: 'Button' });
 */

4.3 Internal Code and Best Practices

  • Internal Code: Use minimal JSDoc (description, key params, returns). Focus on "why" rather than "what".
  • Conciseness: Keep descriptions brief but informative.
  • Accuracy: Update JSDoc immediately when signatures change.
  • Types: Use TS types in JSDoc ONLY when they add clarity or differ from the implementation.

5. Quality Control & Validation

Agents MUST validate all code outputs using the project's quality suite:

  1. Linting: npm run test:lint (Ensures style consistency)
  2. Type Checking: npm run test:types (tsc validation)
  3. Documentation: npm run test:spell-docs (CSpell validation)
  4. Testing: npm run test (Unit) and npm run test:integration (E2E)