Skip to main content
Glama
decision-schema.ts6.24 kB
import { z } from 'zod'; const tagSchema = z.object({ key: z.string(), value: z.string(), }); const decisionStatusSchema = z.enum(['active', 'superseded', 'reversed']); const alternativeSchema = z.object({ option: z.string(), reasoning: z.string(), whyNotChosen: z.string().optional(), }); const decisionDataSchema = z.object({ title: z.string().optional(), question: z.string().optional(), context: z.string().optional(), decision: z.string().optional(), consequences: z.string().optional(), alternativesConsidered: z.array(alternativeSchema).optional(), impactScope: z.array(z.string()).optional(), tags: z.array(tagSchema).optional(), }); const decisionUpdatesSchema = z.object({ title: z.string().optional(), context: z.string().optional(), decision: z.string().optional(), consequences: z.string().optional(), }); const supersedeSchema = z.object({ newDecision: z.string(), reason: z.string(), }); // REFACTOR: Structured schema for newDecision in supersede action const newDecisionSchema = z.object({ decision: z.string().optional(), context: z.string().optional(), consequences: z.string().optional(), }); // Base schema with all fields const baseDecisionSchema = z.object({ action: z.enum(['record', 'get', 'get_many', 'update', 'list', 'supersede', 'get_history', 'diff', 'list_fields']), planId: z.string(), decisionId: z.string().optional(), decisionIds: z.array(z.string()).optional(), decision: decisionDataSchema.optional(), updates: decisionUpdatesSchema.optional(), supersede: supersedeSchema.optional(), // For update action with supersede option newDecision: newDecisionSchema.optional(), // REFACTOR: Structured schema for supersede action reason: z.string().optional(), status: decisionStatusSchema.optional(), // REFACTOR: Removed supersededBy and supersedes - they are entity output fields, not MCP input parameters fields: z.array(z.string()).optional(), excludeMetadata: z.boolean().optional(), // For diff action version1: z.number().optional(), version2: z.number().optional(), }); // Type for the base schema type DecisionInput = z.infer<typeof baseDecisionSchema>; // Schema with superRefine for required field validation based on action export const decisionSchema = baseDecisionSchema.superRefine((data: DecisionInput, ctx) => { // H-2 FIX: status parameter is only valid for 'list' action if (data.status !== undefined && data.action !== 'list') { ctx.addIssue({ code: 'custom', message: `status parameter is only allowed for 'list' action, not '${data.action}'`, path: ['status'], }); } switch (data.action) { case 'record': // decision object and decision.title, decision.question, decision.decision are required if (data.decision === undefined) { ctx.addIssue({ code: 'custom', message: 'decision object is required for record action', path: ['decision'], }); return; } if (typeof data.decision.title !== 'string' || data.decision.title === '') { ctx.addIssue({ code: 'custom', message: 'decision.title is required for record action', path: ['decision', 'title'], }); } if (typeof data.decision.question !== 'string' || data.decision.question === '') { ctx.addIssue({ code: 'custom', message: 'decision.question is required for record action', path: ['decision', 'question'], }); } if (typeof data.decision.decision !== 'string' || data.decision.decision === '') { ctx.addIssue({ code: 'custom', message: 'decision.decision is required for record action', path: ['decision', 'decision'], }); } break; case 'get': case 'update': case 'get_history': case 'diff': // decisionId is required if (typeof data.decisionId !== 'string' || data.decisionId === '') { ctx.addIssue({ code: 'custom', message: `decisionId is required for ${data.action} action`, path: ['decisionId'], }); } break; case 'supersede': // BUG-014 FIX: decisionId, newDecision, newDecision.decision, and reason are all required if (typeof data.decisionId !== 'string' || data.decisionId === '') { ctx.addIssue({ code: 'custom', message: 'decisionId is required for supersede action', path: ['decisionId'], }); } if (data.newDecision === undefined) { ctx.addIssue({ code: 'custom', message: 'newDecision is required for supersede action', path: ['newDecision'], }); } else { // Validate newDecision.decision is required and non-empty if (typeof data.newDecision.decision !== 'string' || data.newDecision.decision === '') { ctx.addIssue({ code: 'custom', message: 'newDecision.decision is required for supersede action', path: ['newDecision', 'decision'], }); } } if (typeof data.reason !== 'string' || data.reason === '') { ctx.addIssue({ code: 'custom', message: 'reason is required for supersede action', path: ['reason'], }); } break; case 'get_many': // decisionIds is required if (!Array.isArray(data.decisionIds) || data.decisionIds.length === 0) { ctx.addIssue({ code: 'custom', message: 'decisionIds is required for get_many action', path: ['decisionIds'], }); } break; case 'list': case 'list_fields': // Only planId is required (already validated by base schema) break; } }); export const decisionToolDescription = 'Record architectural decisions (ADR pattern) with context and alternatives considered. Use after solution selection or for any significant technical choice. Decisions can be superseded when context changes, maintaining decision history. Link decisions to requirements/solutions for traceability. Actions: record, get, get_many, update, list, supersede, get_history, diff.';

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cppmyjob/cpp-mcp-planner'

If you have feedback or need assistance with the MCP directory API, please join our Discord server