mcp-chain-of-draft-server
Version:
A Model Context Protocol server which provides Chain of Draft style thinking
258 lines (257 loc) • 11.3 kB
JavaScript
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { TOOL_NAME, TOOL_SCHEMA, TOOL_DESCRIPTION } from "./apiBluprintDesignerParams.js";
import { SessionManagerFactory } from "../utils/sessionManagerFactory.js";
const apiHistory = {};
const activeCritiques = {};
export const apiBlueprintDesignerTool = (server) => {
const processAPIRequest = async (input) => {
try {
const validatedInput = validateAPIBlueprintData(input);
if (!validatedInput) {
throw new McpError(ErrorCode.InvalidParams, "Invalid API blueprint 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().getAPIBlueprintManager();
const session = await sessionManager.getSession(validatedInput.api_id);
// Initialize session data if needed
if (!session.data.apiHistory) {
session.data.apiHistory = [];
}
if (!session.data.activeCritiques) {
session.data.activeCritiques = [];
}
// Store the API blueprint in history
session.data.apiHistory.push(validatedInput);
// Handle critique tracking
if (validatedInput.is_critique && validatedInput.critique_focus) {
session.data.activeCritiques.push(validatedInput.critique_focus);
}
// Update session
await sessionManager.updateSession(validatedInput.api_id, session.data);
// Format response
return {
content: [{
type: "text",
text: JSON.stringify({
apiId: validatedInput.api_id,
apiName: validatedInput.api_name,
apiVersion: validatedInput.api_version,
draftNumber: validatedInput.draft_number,
totalDrafts: validatedInput.total_drafts,
nextStepNeeded: validatedInput.next_step_needed,
isCritique: validatedInput.is_critique,
critiqueFocus: validatedInput.critique_focus,
revisionInstructions: validatedInput.revision_instructions,
endpointCount: validatedInput.endpoints.length,
activeCritiques: session.data.activeCritiques,
apiHistoryLength: session.data.apiHistory.length,
isFinalDraft: validatedInput.is_final_draft,
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 processAPIRequest(args))
}]
}));
};
const validateAPIBlueprintData = (input) => {
const data = input;
// Check required fields first
if (typeof data !== "object" || data === null ||
!("api_id" in data) ||
!("api_name" in data) ||
!("api_version" in data) ||
!("description" in data) ||
!("endpoints" in data) ||
!("auth_requirements" in data) ||
!("draft_number" in data) ||
!("total_drafts" in data) ||
!("next_step_needed" in data)) {
throw new Error("Missing required fields");
}
// Validate endpoints
if (!Array.isArray(data.endpoints)) {
throw new Error("Endpoints must be an array");
}
const validMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
const validParamLocations = ["path", "query", "header", "cookie"];
const validAuthTypes = ["none", "basic", "bearer", "api_key", "oauth2", "custom"];
const endpoints = [];
for (const ep of data.endpoints) {
if (typeof ep !== "object" || ep === null ||
!("path" in ep) ||
!("method" in ep) ||
!("description" in ep) ||
!("parameters" in ep) ||
!("responses" in ep)) {
throw new Error("Each endpoint must have path, method, description, parameters, and responses");
}
// Validate method
if (!validMethods.includes(String(ep.method))) {
throw new Error(`Invalid method. Must be one of: ${validMethods.join(", ")}`);
}
// Validate parameters
if (!Array.isArray(ep.parameters)) {
throw new Error("Parameters must be an array");
}
const parameters = [];
for (const param of ep.parameters) {
if (typeof param !== "object" || param === null ||
!("name" in param) ||
!("location" in param) ||
!("required" in param) ||
!("type" in param) ||
!("description" in param)) {
throw new Error("Each parameter must have name, location, required, type, and description");
}
// Validate location
if (!validParamLocations.includes(String(param.location))) {
throw new Error(`Invalid parameter location. Must be one of: ${validParamLocations.join(", ")}`);
}
parameters.push({
name: String(param.name),
location: String(param.location),
required: Boolean(param.required),
type: String(param.type),
description: String(param.description)
});
}
// Validate request body (if present)
let requestBody = undefined;
if ("request_body" in ep && ep.request_body) {
const rb = ep.request_body;
if (typeof rb !== "object" || rb === null ||
!("content_type" in rb) ||
!("schema" in rb) ||
!("example" in rb)) {
throw new Error("Request body must have content_type, schema, and example");
}
requestBody = {
content_type: String(rb.content_type),
schema: rb.schema,
example: String(rb.example)
};
}
// Validate responses
if (!Array.isArray(ep.responses) || ep.responses.length === 0) {
throw new Error("Responses must be a non-empty array");
}
const responses = [];
for (const resp of ep.responses) {
if (typeof resp !== "object" || resp === null ||
!("status_code" in resp) ||
!("description" in resp) ||
!("content_type" in resp) ||
!("schema" in resp) ||
!("example" in resp)) {
throw new Error("Each response must have status_code, description, content_type, schema, and example");
}
// Validate status code
const statusCode = Number(resp.status_code);
if (isNaN(statusCode) || statusCode < 100 || statusCode > 599) {
throw new Error("Status code must be between 100 and 599");
}
responses.push({
status_code: statusCode,
description: String(resp.description),
content_type: String(resp.content_type),
schema: resp.schema,
example: String(resp.example)
});
}
endpoints.push({
path: String(ep.path),
method: String(ep.method),
description: String(ep.description),
parameters: parameters,
request_body: requestBody,
responses: responses
});
}
// Validate auth requirements
if (typeof data.auth_requirements !== "object" || data.auth_requirements === null ||
!("type" in data.auth_requirements) ||
!("description" in data.auth_requirements)) {
throw new Error("Auth requirements must have type and description");
}
const authType = String(data.auth_requirements.type);
if (!validAuthTypes.includes(authType)) {
throw new Error(`Invalid auth type. Must be one of: ${validAuthTypes.join(", ")}`);
}
const authRequirements = {
type: authType,
description: String(data.auth_requirements.description)
};
// Create and validate API blueprint data object
const apiBlueprintData = {
api_id: String(data.api_id),
api_name: String(data.api_name),
api_version: String(data.api_version),
description: String(data.description),
endpoints: endpoints,
auth_requirements: authRequirements,
draft_number: Number(data.draft_number),
total_drafts: Number(data.total_drafts),
next_step_needed: Boolean(data.next_step_needed)
};
// Optional fields
if ("is_critique" in data)
apiBlueprintData.is_critique = Boolean(data.is_critique);
if ("critique_focus" in data)
apiBlueprintData.critique_focus = String(data.critique_focus);
if ("revision_instructions" in data)
apiBlueprintData.revision_instructions = String(data.revision_instructions);
if ("is_final_draft" in data)
apiBlueprintData.is_final_draft = Boolean(data.is_final_draft);
// Additional validations
if (apiBlueprintData.draft_number <= 0) {
throw new Error("Draft number must be positive");
}
if (apiBlueprintData.total_drafts <= 0) {
throw new Error("Total drafts must be positive");
}
if (apiBlueprintData.draft_number > apiBlueprintData.total_drafts) {
throw new Error("Draft number cannot exceed total drafts");
}
// Validate critique-specific fields
if (apiBlueprintData.is_critique && !apiBlueprintData.critique_focus) {
throw new Error("Critique focus required when is_critique is true");
}
// Validate revision-specific fields
if (apiBlueprintData.is_critique === false && !apiBlueprintData.revision_instructions) {
throw new Error("Revision instructions required when is_critique is false");
}
return apiBlueprintData;
};
const formatAPIBlueprint = (api) => {
return JSON.stringify({
apiId: api.api_id,
apiName: api.api_name,
apiVersion: api.api_version,
description: api.description,
endpoints: api.endpoints.map(ep => ({
path: ep.path,
method: ep.method,
description: ep.description,
parameterCount: ep.parameters.length,
responseCount: ep.responses.length
})),
authType: api.auth_requirements.type,
draftNumber: api.draft_number
}, null, 2);
};