mcp-chain-of-draft-server
Version:
A Model Context Protocol server which provides Chain of Draft style thinking
160 lines (159 loc) • 6.71 kB
JavaScript
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { TOOL_NAME, TOOL_SCHEMA, TOOL_DESCRIPTION } from "./architectureDecisionRecorderParams.js";
import { SessionManagerFactory } from "../utils/sessionManagerFactory.js";
export const architectureDecisionRecorderTool = (server) => {
const processADRRequest = async (input) => {
try {
const validatedInput = validateADRData(input);
if (!validatedInput) {
throw new McpError(ErrorCode.InvalidParams, "Invalid ADR data");
}
// Validate draft progression
if (validatedInput.draft_number > validatedInput.total_drafts) {
validatedInput.total_drafts = validatedInput.draft_number;
}
// Get session manager and retrieve/create session
const sessionManager = SessionManagerFactory.getInstance().getADRManager();
const session = await sessionManager.getSession(validatedInput.id);
// Initialize session data if needed
if (!session.data.adrHistory) {
session.data.adrHistory = [];
}
// Store the ADR in history
session.data.adrHistory.push(validatedInput);
// Update session
await sessionManager.updateSession(validatedInput.id, session.data);
// Format response
return {
content: [{
type: "text",
text: JSON.stringify({
id: validatedInput.id,
title: validatedInput.title,
status: validatedInput.status,
draftNumber: validatedInput.draft_number,
totalDrafts: validatedInput.total_drafts,
isCritique: validatedInput.is_critique,
critiqueFocus: validatedInput.critique_focus,
revisionInstructions: validatedInput.revision_instructions,
isFinalDraft: validatedInput.is_final_draft,
historyLength: session.data.adrHistory.length,
sessionMetadata: session.metadata
}, null, 2)
}]
};
}
catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(ErrorCode.InternalError, error instanceof Error ? error.message : String(error));
}
};
server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_SCHEMA, async (args, extra) => ({
content: [{
type: "text",
text: JSON.stringify(await processADRRequest(args))
}]
}));
};
const validateADRData = (input) => {
const data = input;
// Check only required fields first
if (typeof data !== "object" || data === null ||
!("id" in data) ||
!("title" in data) ||
!("status" in data) ||
!("context" in data) ||
!("decision" in data) ||
!("consequences" in data) ||
!("alternatives" in data) ||
!("related_decisions" in data) ||
!("draft_number" in data) ||
!("total_drafts" in data)) {
throw new Error("Missing required fields");
}
// Validate types and create return object
const adrData = {
id: String(data.id),
title: String(data.title),
status: String(data.status),
context: String(data.context),
decision: String(data.decision),
consequences: Array.isArray(data.consequences) ? data.consequences.map(String) : [],
alternatives: Array.isArray(data.alternatives) ? data.alternatives : [],
related_decisions: Array.isArray(data.related_decisions) ? data.related_decisions.map(String) : [],
draft_number: Number(data.draft_number),
total_drafts: Number(data.total_drafts),
is_final_draft: Boolean(data.is_final_draft ?? false)
};
// Optional fields
if ("is_critique" in data)
adrData.is_critique = Boolean(data.is_critique);
if ("critique_focus" in data)
adrData.critique_focus = String(data.critique_focus);
if ("revision_instructions" in data)
adrData.revision_instructions = String(data.revision_instructions);
// Validate the data
if (!adrData.id || adrData.id.trim() === "") {
throw new Error("Invalid ID");
}
if (!adrData.title || adrData.title.trim() === "") {
throw new Error("Invalid title");
}
const validStatuses = ["proposed", "accepted", "rejected", "deprecated", "superseded"];
if (!validStatuses.includes(adrData.status)) {
throw new Error("Invalid status");
}
if (!adrData.context || adrData.context.trim() === "") {
throw new Error("Invalid context");
}
if (!adrData.decision || adrData.decision.trim() === "") {
throw new Error("Invalid decision");
}
if (!Array.isArray(adrData.consequences)) {
throw new Error("Invalid consequences");
}
if (!Array.isArray(adrData.alternatives)) {
throw new Error("Invalid alternatives");
}
if (!Array.isArray(adrData.related_decisions)) {
throw new Error("Invalid related decisions");
}
if (adrData.draft_number <= 0) {
throw new Error("Draft number must be positive");
}
if (adrData.total_drafts <= 0) {
throw new Error("Total drafts must be positive");
}
if (adrData.draft_number > adrData.total_drafts) {
throw new Error("Draft number cannot exceed total drafts");
}
// Validate critique-specific fields
if (adrData.is_critique && !adrData.critique_focus) {
throw new Error("Critique focus required when is_critique is true");
}
// Validate revision-specific fields
if (adrData.is_critique === false && !adrData.revision_instructions) {
throw new Error("Revision instructions required when is_critique is false");
}
return adrData;
};
const formatADR = (adr) => {
return JSON.stringify({
id: adr.id,
title: adr.title,
status: adr.status,
context: adr.context,
decision: adr.decision,
consequences: adr.consequences,
alternatives: adr.alternatives,
related_decisions: adr.related_decisions,
draft_number: adr.draft_number,
total_drafts: adr.total_drafts,
is_critique: adr.is_critique,
critique_focus: adr.critique_focus,
revision_instructions: adr.revision_instructions,
is_final_draft: adr.is_final_draft
});
};