semantic-prompt-mcp
Version:
MCP server for semantic prompt framework - NLP-inspired adaptive reasoning engine for LLM orchestration
885 lines • 42.8 kB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
// Fixed chalk import for ESM
import chalk from 'chalk';
import { homedir } from 'os';
import { join, dirname } from 'path';
import { readFileSync, existsSync, readdirSync } from 'fs';
import { fileURLToPath } from 'url';
// Get directory of current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load prompts configuration with flexible priority
const loadPrompts = () => {
let promptFile;
// Priority 1: Command line argument (--prompt filename.json or just filename.json)
const args = process.argv.slice(2);
const promptArgIndex = args.findIndex(arg => arg === '--prompt');
if (promptArgIndex !== -1 && args[promptArgIndex + 1]) {
promptFile = args[promptArgIndex + 1];
}
else if (args.length > 0 && args[0].endsWith('.json')) {
promptFile = args[0];
}
// Priority 2: Environment variable
if (!promptFile && process.env.CHAIN_OF_THOUGHT_CONFIG) {
promptFile = process.env.CHAIN_OF_THOUGHT_CONFIG;
}
// Priority 3: Default fallback (no custom.json auto-detection)
if (!promptFile) {
promptFile = 'default.json';
}
// Resolve the path - look for prompts in the source directory
const promptPath = promptFile.includes('/') || promptFile.includes('\\')
? promptFile
: join(__dirname, '..', 'prompts', promptFile);
try {
return JSON.parse(readFileSync(promptPath, 'utf-8'));
}
catch (error) {
console.error(`Failed to load prompts from ${promptPath}:`, error);
// Fallback to hardcoded minimal prompts (provide keys that code expects)
return {
errors: {
invalidThought: "Invalid thought: must be a string",
invalidThoughtNumber: "Invalid thoughtNumber: must be a number",
invalidTotalThoughts: "Invalid totalThoughts: must be a number",
invalidNextThoughtNeeded: "Invalid nextThoughtNeeded: must be a boolean",
commandFieldRequired: "commandSelection type \"command\" requires a \"command\" field",
reasonFieldRequired: "commandSelection type \"{type}\" requires a \"reason\" field",
invalidSelectionType: "Invalid commandSelection type: {type}. Must be \"command\", \"skip\", or \"skip_reason\"",
unexpectedThoughtNumber: "Unexpected thought number: {number}.",
mandatoryViolation: "Step {step} requires commandSelection",
unknownTool: "Unknown tool: {name}"
},
messages: {
duplicateDocumentRead: "Document already read this session: {command}",
serverRunning: "MCP Server running",
fatalError: "Fatal error: {error}"
},
console: {
thoughtPrefix: { default: "Thought", revision: "Revision", branch: "Branch" },
thoughtContext: { revision: " (revising thought {number})", branch: " (from thought {from}, ID: {id})" }
},
config: {}
};
}
};
const prompts = loadPrompts();
class SemanticPromptServer {
thoughtHistory = [];
branches = {};
disableThoughtLogging;
selectedCommand = null;
readFiles = new Set();
config;
hasCommandBeenSelected = false;
// Agents extracted from last-read TOML command
extractedAgentsFromToml = [];
extractedFromCommand = null;
constructor(config) {
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
// Default configuration (can be overridden)
this.config = {
commandPath: config?.commandPath || '',
commandExtension: config?.commandExtension || '.md', // Default to .md
availableCommands: config?.availableCommands || [],
requireSelectionByStep: config?.requireSelectionByStep ?? 2,
enableAutoRead: config?.enableAutoRead ?? true,
agentsPath: config?.agentsPath || '~/.gemini/agents/',
agentsExtension: config?.agentsExtension || '.md',
historyMaxLength: typeof config?.historyMaxLength === 'number' ? config?.historyMaxLength : 200,
...config
};
}
// Extract agents array from TOML content in a permissive way
parseAgentsFromToml(tomlContent) {
try {
const match = tomlContent.match(/\bagents\s*=\s*\[([\s\S]*?)\]/i);
if (!match)
return [];
const inner = match[1];
const items = inner.split(',');
const safe = /^[a-z0-9-]+$/;
const agents = items
.map(s => s.replace(/#.*$/m, '')) // remove comments in the same slice
.map(s => s.trim())
.map(s => s.replace(/^['"]/, '').replace(/['"]$/, ''))
.map(s => s.toLowerCase())
.filter(Boolean)
.filter(s => safe.test(s));
const seen = new Set();
return agents.filter(a => (seen.has(a) ? false : (seen.add(a), true)));
}
catch {
return [];
}
}
expandPath(path) {
if (path.startsWith('~/')) {
return join(homedir(), path.slice(2));
}
return path;
}
// Resolve available agents from config or by scanning the agents directory
resolveAvailableAgents() {
const safe = /^[a-z0-9-]+$/;
if (this.config.availableAgents && this.config.availableAgents.length > 0) {
return this.config.availableAgents
.map(a => a.toLowerCase())
.filter(a => safe.test(a));
}
try {
const base = this.expandPath(this.config.agentsPath || '~/.gemini/agents/');
const ext = (this.config.agentsExtension || '.md').toLowerCase();
const entries = readdirSync(base, { withFileTypes: true });
return entries
.filter(e => e.isFile())
.map(e => e.name)
.filter(name => name.toLowerCase().endsWith(ext))
.map(name => name.slice(0, name.length - ext.length).toLowerCase())
.filter(a => safe.test(a));
}
catch {
return [];
}
}
// Simple parameter normalizer for handling various AI input formats
normalizeParameters(input) {
if (!input || typeof input !== 'object')
return input;
const result = { ...input };
// Handle XML-like strings in commandSelection/agentSelection
['commandSelection', 'agentSelection'].forEach(key => {
if (typeof result[key] === 'string' && result[key].includes('<parameter')) {
const parsed = {};
// Extract XML parameters
const matches = result[key].matchAll(/name=["']([^"']+)["']>([^<]+)</g);
for (const match of matches) {
parsed[match[1]] = match[2].trim();
}
result[key] = parsed;
}
else if (typeof result[key] === 'string') {
// Try JSON parse
try {
result[key] = JSON.parse(result[key]);
}
catch { }
}
});
// Convert string numbers to numbers
['thoughtNumber', 'totalThoughts', 'revisesThought', 'branchFromThought'].forEach(key => {
if (typeof result[key] === 'string') {
const num = parseInt(result[key], 10);
if (!isNaN(num))
result[key] = num;
}
});
// Convert string booleans to booleans
['nextThoughtNeeded', 'isRevision', 'needsMoreThoughts'].forEach(key => {
if (result[key] === 'true')
result[key] = true;
else if (result[key] === 'false')
result[key] = false;
});
return result;
}
validateThoughtData(input) {
// Apply normalization first
const normalized = this.normalizeParameters(input);
const data = normalized;
if (!data.thought || typeof data.thought !== 'string') {
throw new Error(prompts.errors.invalidThought);
}
// Coerce numeric strings to numbers for robustness
if (typeof data.thoughtNumber === 'string') {
const n = parseInt(data.thoughtNumber, 10);
if (!Number.isNaN(n))
data.thoughtNumber = n;
}
if (!data.thoughtNumber || typeof data.thoughtNumber !== 'number') {
throw new Error(prompts.errors.invalidThoughtNumber);
}
if (typeof data.totalThoughts === 'string') {
const n = parseInt(data.totalThoughts, 10);
if (!Number.isNaN(n))
data.totalThoughts = n;
}
if (!data.totalThoughts || typeof data.totalThoughts !== 'number') {
throw new Error(prompts.errors.invalidTotalThoughts);
}
// Fix: Handle various formats of nextThoughtNeeded (Gemini sends different types)
if (data.nextThoughtNeeded === undefined || data.nextThoughtNeeded === null) {
data.nextThoughtNeeded = true; // Default to true if missing
}
if (typeof data.nextThoughtNeeded !== 'boolean') {
// Try to convert to boolean
data.nextThoughtNeeded = Boolean(data.nextThoughtNeeded);
}
return {
thought: data.thought,
thoughtNumber: data.thoughtNumber,
totalThoughts: data.totalThoughts,
nextThoughtNeeded: Boolean(data.nextThoughtNeeded), // Ensure it's always boolean
commandSelection: data.commandSelection,
agentSelection: data.agentSelection,
isRevision: data.isRevision,
revisesThought: data.revisesThought,
branchFromThought: data.branchFromThought,
branchId: data.branchId,
needsMoreThoughts: data.needsMoreThoughts,
};
}
recordThought(thoughtData) {
this.thoughtHistory.push(thoughtData);
const maxLength = this.config.historyMaxLength ?? 200;
if (maxLength > 0 && this.thoughtHistory.length > maxLength) {
const overflow = this.thoughtHistory.length - maxLength;
this.thoughtHistory.splice(0, overflow);
}
}
formatThought(thoughtData) {
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId } = thoughtData;
let prefix = '';
let context = '';
if (isRevision) {
prefix = chalk.yellow(prompts.console.thoughtPrefix.revision);
context = prompts.console.thoughtContext.revision.replace('{number}', String(revisesThought));
}
else if (branchFromThought) {
prefix = chalk.green(prompts.console.thoughtPrefix.branch);
context = prompts.console.thoughtContext.branch
.replace('{from}', String(branchFromThought))
.replace('{id}', branchId || '');
}
else {
prefix = chalk.blue(prompts.console.thoughtPrefix.default);
context = '';
}
const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`;
const border = '─'.repeat(Math.max(header.length, thought.length) + 4);
return `
┌${border}┐
│ ${header} │
├${border}┤
│ ${thought.padEnd(border.length - 2)} │
└${border}┘`;
}
processCommandSelection(selection) {
let documentStatus = null;
let documentContent;
// Fix: Handle when Claude/Gemini sends command name as type instead of "command"
// e.g., type: "cleanup" instead of type: "command", command: "cleanup"
if (selection && selection.type) {
const rawType = String(selection.type).toLowerCase();
if (rawType !== "command" && rawType !== "skip" && rawType !== "skip_reason") {
// If type is actually a command name
if (this.config.availableCommands && this.config.availableCommands.includes(rawType)) {
selection = { type: "command", command: rawType };
}
else if (rawType.includes('command')) {
// Handle markup like "<parameter name=\"type\">command"
selection.type = 'command';
}
}
}
else if (selection?.command && !selection?.type) {
// If type missing but command present, assume command
selection.type = 'command';
}
switch (selection.type) {
case "command":
if (!selection.command) {
throw new Error(prompts.errors.commandFieldRequired);
}
// Normalize and validate command name (security)
// Remove leading slash if present (AI sometimes adds it by mistake)
selection.command = selection.command.toLowerCase().replace(/^\/+/, '');
const safeNamePattern = /^[a-z0-9-]+$/;
// Validate command exists in availableCommands list before processing
if (this.config.availableCommands && this.config.availableCommands.length > 0) {
const commandExists = this.config.availableCommands.includes(selection.command);
if (!commandExists) {
throw new Error(`Command '${selection.command}' not found. Available commands: ${this.config.availableCommands.join(', ')}`);
}
}
else {
// If no allow-list configured, enforce safe pattern to prevent path traversal
if (!safeNamePattern.test(selection.command)) {
throw new Error(`Invalid command name '${selection.command}'. Use lowercase letters, numbers, and hyphens only.`);
}
}
// Check for duplicate document read (SuperClaude Framework SSOT principle - Single Source of Truth)
const commandKey = selection.command.toLowerCase();
if (this.readFiles.has(commandKey)) {
// Document already read - return system reminder message
documentStatus = "system-reminder";
documentContent = prompts.messages?.duplicateDocumentRead?.replace('{command}', selection.command) ||
`Document already read this session: ${selection.command}.md\n\nPlease refer to system-reminder content and apply that information to proceed with next step analysis.`;
this.selectedCommand = commandKey;
this.hasCommandBeenSelected = true;
return { documentStatus, documentContent };
}
this.selectedCommand = commandKey;
this.hasCommandBeenSelected = true;
const extension = this.config.commandExtension || '.md';
documentStatus = `${selection.command}${extension}`;
// Use configured path and extension (safe join)
const basePath = this.expandPath(this.config.commandPath || '');
const expandedFileToRead = join(basePath, `${selection.command}${extension}`);
// Try to read the document content directly
try {
if (existsSync(expandedFileToRead)) {
documentContent = readFileSync(expandedFileToRead, 'utf-8');
// Add to read files set (SSOT principle)
this.readFiles.add(commandKey);
// If TOML, extract agents for Step 3 suggestions
const isToml = (this.config.commandExtension || '.md').toLowerCase() === '.toml'
|| expandedFileToRead.toLowerCase().endsWith('.toml');
if (isToml && typeof documentContent === 'string') {
const extracted = this.parseAgentsFromToml(documentContent);
this.extractedAgentsFromToml = extracted;
this.extractedFromCommand = commandKey;
}
else {
this.extractedAgentsFromToml = [];
this.extractedFromCommand = null;
}
}
}
catch (error) {
console.error(`Failed to read command document ${expandedFileToRead}:`, error);
// If file doesn't exist or can't be read, set a fallback message
documentContent = `Error: Could not read command document at ${expandedFileToRead}`;
}
break;
case "skip":
if (!selection.reason) {
throw new Error(prompts.errors.reasonFieldRequired.replace('{type}', 'skip'));
}
this.selectedCommand = "skip";
this.hasCommandBeenSelected = true;
documentStatus = "skip";
documentContent = `Skip reason: ${selection.reason}`;
break;
case "skip_reason":
if (!selection.reason) {
throw new Error(prompts.errors.reasonFieldRequired.replace('{type}', 'skip_reason'));
}
// Extract command from reason if it follows pattern
const commandFromReason = selection.reason.match(/(\w+)\.md/);
if (commandFromReason) {
this.selectedCommand = commandFromReason[1].toLowerCase();
}
this.hasCommandBeenSelected = true;
documentStatus = "system-reminder";
documentContent = `System reminder: ${selection.reason}`;
break;
default:
throw new Error(prompts.errors.invalidSelectionType.replace('{type}', selection.type));
}
return { documentStatus, documentContent };
}
processAgentSelection(selection) {
let documentStatus = null;
let documentContent;
switch (selection.type) {
case "agents":
if (!selection.agents || selection.agents.length === 0) {
throw new Error("Agent selection type 'agents' requires an 'agents' array");
}
// Validate against available agents and provide guidance on invalid ones
{
const available = new Set(this.resolveAvailableAgents());
const invalid = selection.agents
.map(a => a.toLowerCase())
.filter(a => !available.has(a));
if (invalid.length > 0) {
const availableList = Array.from(available).join(', ') || 'N/A';
documentStatus = "agents-invalid";
documentContent = `Invalid agents: ${invalid.join(', ')}\n\nPlease choose within: ${availableList}`;
break;
}
}
// If all selected agents already read, consolidate into system-reminder response
{
const selectedLower = selection.agents.map(a => a.toLowerCase());
const allAlreadyRead = selectedLower.every(a => this.readFiles.has(`agent-${a}`));
if (allAlreadyRead) {
documentStatus = "system-reminder";
const selectedList = selection.agents.join(', ');
const template = prompts.templates?.agentDuplicateReminder
|| prompts.messages?.useSystemReminderAgents
|| `Agent documents already read: ${selectedList}\n\nPlease refer to system-reminder content and apply agent perspectives to proceed.`;
documentContent = template
.replaceAll('{agents}', selectedList)
.replaceAll('{agentList}', selectedList);
break;
}
}
documentStatus = "agents-selected";
{
// Check if agent documents should be returned
if (this.config.returnAgentDocuments === false) {
// SuperClaude mode: Task tool command format
const agentList = selection.agents.join(', ');
if (selection.agents.length === 1) {
documentContent = `**Agent Selection Complete**\n\nSelected agent: ${selection.agents[0]}. Use Task tool with ${selection.agents[0]} agent.`;
}
else {
documentContent = `**Agent Selection Complete**\n\nSelected agents: ${agentList}. Use Task tool with appropriate agent based on task needs.`;
}
// Still mark as read for tracking
for (const agent of selection.agents) {
const agentKey = agent.toLowerCase();
const safeNamePattern = /^[a-z0-9-]+$/;
if (safeNamePattern.test(agentKey)) {
this.readFiles.add(`agent-${agentKey}`);
}
}
}
else {
// SuperGemini mode: Return full agent document contents
let agentContents = `**SuperGemini Agent Selection Complete**\n\n`;
agentContents += `Selected agents: [${selection.agents.map(a => `"${a}"`).join(', ')}]\n\n`;
for (const agent of selection.agents) {
const agentKey = agent.toLowerCase();
const safeNamePattern = /^[a-z0-9-]+$/;
if (!safeNamePattern.test(agentKey)) {
continue;
}
if (this.readFiles.has(`agent-${agentKey}`)) {
agentContents += `\n### ${agent}.md (already read)\nRefer to system-reminder content for this agent.\n`;
continue;
}
const agentsBase = this.expandPath(this.config.agentsPath || '~/.gemini/agents/');
const agentsExt = this.config.agentsExtension || '.md';
const agentPath = join(agentsBase, `${agent}${agentsExt}`);
try {
if (existsSync(agentPath)) {
const content = readFileSync(agentPath, 'utf-8');
agentContents += `\n### ${agent}.md\n${content}\n`;
this.readFiles.add(`agent-${agentKey}`);
}
else {
agentContents += `\n### ${agent}.md\nError: Agent file not found at ${agentPath}\n`;
}
}
catch (error) {
agentContents += `\n### ${agent}.md\nError: Could not read agent file - ${error}\n`;
}
}
documentContent = agentContents;
}
}
break;
case "skip":
if (!selection.reason) {
throw new Error("Agent selection type 'skip' requires a 'reason' field");
}
documentStatus = "skip";
documentContent = `Skip reason: ${selection.reason}`;
break;
case "skip_reason":
if (!selection.reason) {
throw new Error("Agent selection type 'skip_reason' requires a 'reason' field");
}
documentStatus = "system-reminder";
documentContent = `System reminder: ${selection.reason}`;
break;
default:
throw new Error(`Invalid agent selection type: ${selection.type}`);
}
return { documentStatus, documentContent };
}
processStep1(thoughtData) {
// STEP 1: Focus only on understanding user request
// Optional command selection processing (not mandatory)
let documentStatus = null;
let documentContent;
// Optional commandSelection processing for Step 1
if (thoughtData.commandSelection) {
const result = this.processCommandSelection(thoughtData.commandSelection);
documentStatus = result.documentStatus;
documentContent = result.documentContent;
}
this.recordThought(thoughtData);
if (!this.disableThoughtLogging) {
const formattedThought = this.formatThought(thoughtData);
console.error(formattedThought);
}
const response = {
thoughtNumber: 1,
totalThoughts: thoughtData.totalThoughts,
nextThoughtNeeded: thoughtData.nextThoughtNeeded,
documentStatus
};
// Add document content to response if available
if (documentContent) {
response.documentContent = documentContent;
}
const contents = [{
type: "text",
text: JSON.stringify(response, null, 2)
}];
return {
content: contents
};
}
validateAndProcessStep(thoughtData, stepNumber) {
let documentStatus = null;
let documentContent;
let forceNextThought = false;
// Check if command selection is needed at this step
const needsCommand = stepNumber >= this.config.requireSelectionByStep && !this.hasCommandBeenSelected;
if (needsCommand && !thoughtData.commandSelection) {
const availableCommands = this.config.availableCommands?.join(', ') || 'No commands configured';
// Provide clearer error message with example
const errorMessage = prompts.errors.mandatoryViolation
.replace('{step}', String(stepNumber))
.replace('{commands}', availableCommands)
.replace('{path}', this.config.commandPath);
// Add example to help AI understand the format
const exampleCommand = (this.config.availableCommands && this.config.availableCommands.length > 0)
? this.config.availableCommands[0]
: 'command-name';
const examplePayload = { commandSelection: { type: 'command', command: exampleCommand } };
throw new Error(errorMessage + '\n\nExample:\n' + JSON.stringify(examplePayload));
}
// Process commandSelection if provided
if (thoughtData.commandSelection) {
const result = this.processCommandSelection(thoughtData.commandSelection);
documentStatus = result.documentStatus;
documentContent = result.documentContent;
}
this.recordThought(thoughtData);
if (!this.disableThoughtLogging) {
const formattedThought = this.formatThought(thoughtData);
console.error(formattedThought);
}
const response = {
thoughtNumber: stepNumber,
totalThoughts: thoughtData.totalThoughts,
nextThoughtNeeded: forceNextThought ? true : thoughtData.nextThoughtNeeded,
documentStatus: documentStatus || null
};
// Add document content to response if available
if (documentContent) {
response.documentContent = documentContent;
}
const contents = [{
type: "text",
text: JSON.stringify(response, null, 2)
}];
return {
content: contents
};
}
processStep2(thoughtData) {
return this.validateAndProcessStep(thoughtData, 2);
}
processStep3(thoughtData) {
// STEP 3: Agent Persona Selection & Reading (same pattern as step 2)
let documentStatus = null;
let documentContent;
let forceNextThought = false;
let suggestedAgents;
let availableAgentsForGuidance;
let extractedFromCommandForGuidance;
// Process agent selection based on configuration
if (!this.config.step3RequiresAgentSelection) {
// Agent selection not required - just continue
if (thoughtData.agentSelection) {
const result = this.processAgentSelection(thoughtData.agentSelection);
documentStatus = result.documentStatus;
documentContent = result.documentContent;
}
else {
documentStatus = 'step3_complete';
documentContent = '';
}
}
else if (thoughtData.agentSelection) {
// Agent selection provided - process it
const result = this.processAgentSelection(thoughtData.agentSelection);
documentStatus = result.documentStatus;
documentContent = result.documentContent;
if (documentStatus === 'agents-invalid') {
forceNextThought = true;
availableAgentsForGuidance = this.resolveAvailableAgents();
}
}
else {
// Agent selection required but not provided
if (this.extractedAgentsFromToml?.length > 0) {
// Suggest agents from TOML
const from = this.extractedFromCommand || this.selectedCommand || 'unknown';
const list = this.extractedAgentsFromToml.join(', ');
documentStatus = "agents-suggested";
const extractedMsg = prompts.messages?.agentExtracted || `Agents extracted from {command}.toml: {agents}`;
documentContent = extractedMsg.replace('{command}', from).replace('{agents}', list) +
'\n\nPlease select agent personas at Step 3 by sending agentSelection { type: "agents", agents: [...] }.';
forceNextThought = true;
suggestedAgents = [...this.extractedAgentsFromToml];
extractedFromCommandForGuidance = from;
}
else {
// No agents found - show available agents
const availableList = this.config.availableAgents?.join(', ') || 'N/A';
documentStatus = "agents-needed";
const msg = prompts.errors?.missingAgents || `No agents found in Step 3. Available agents: {availableAgents}`;
documentContent = msg.replace('{availableAgents}', availableList);
forceNextThought = true;
availableAgentsForGuidance = this.resolveAvailableAgents();
}
}
this.thoughtHistory.push(thoughtData);
if (!this.disableThoughtLogging) {
const formattedThought = this.formatThought(thoughtData);
console.error(formattedThought);
}
const response = {
thoughtNumber: 3,
totalThoughts: thoughtData.totalThoughts,
nextThoughtNeeded: forceNextThought ? true : thoughtData.nextThoughtNeeded,
documentStatus
};
// Add document content to response if available
if (documentContent) {
response.documentContent = documentContent;
}
// Add machine-friendly guidance to help clients proceed reliably
if (suggestedAgents && suggestedAgents.length > 0) {
response.suggestedAgents = suggestedAgents;
}
if (availableAgentsForGuidance && availableAgentsForGuidance.length > 0) {
response.availableAgents = availableAgentsForGuidance;
}
if (extractedFromCommandForGuidance) {
response.extractedFromCommand = extractedFromCommandForGuidance;
}
if (forceNextThought) {
response.requiredAction = 'select_agents';
}
const contents = [{
type: "text",
text: JSON.stringify(response, null, 2)
}];
return {
content: contents
};
}
processThought(input) {
try {
const validatedInput = this.validateThoughtData(input);
// Respect client-provided totalThoughts (no forced override)
// STEP 1: User Input Analysis Only (no command selection yet)
if (validatedInput.thoughtNumber === 1) {
return this.processStep1(validatedInput);
}
// STEP 2: MANDATORY Command/Document Selection
if (validatedInput.thoughtNumber === 2) {
return this.processStep2(validatedInput);
}
// STEP 3: Agent Persona Extraction & Reading
if (validatedInput.thoughtNumber === 3) {
return this.processStep3(validatedInput);
}
// STEP 4: Agent Embodiment & Problem Solving Execution
if (validatedInput.thoughtNumber === 4) {
return this.validateAndProcessStep(validatedInput, 4);
}
// Handle thoughts beyond 4 (expansion if needed)
if (validatedInput.thoughtNumber > 4) {
if (validatedInput.thoughtNumber >= validatedInput.totalThoughts && validatedInput.nextThoughtNeeded) {
validatedInput.totalThoughts = validatedInput.thoughtNumber + 1;
}
return this.validateAndProcessStep(validatedInput, validatedInput.thoughtNumber);
}
// Fallback for unexpected thought numbers
throw new Error(prompts.errors.unexpectedThoughtNumber.replace('{number}', String(validatedInput.thoughtNumber)));
}
catch (error) {
// Simple error handling - just restart the same step
const inputData = this.extractInputData(input);
return {
content: [{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
status: 'failed',
availableCommands: this.config.availableCommands || [],
commandsDefinedIn: this.config.commandPath,
thoughtNumber: inputData.thoughtNumber,
totalThoughts: inputData.totalThoughts,
nextThoughtNeeded: true
}, null, 2)
}],
isError: true
};
}
}
extractInputData(input) {
try {
const inputData = input;
return {
thoughtNumber: inputData?.thoughtNumber || 1,
totalThoughts: inputData?.totalThoughts || 4
};
}
catch {
return { thoughtNumber: 1, totalThoughts: 4 };
}
}
}
const SUPERCLAUDE_THINKING_TOOL = {
name: prompts.tool?.name || 'chain_of_thought',
description: prompts.tool?.description || 'Dynamic thinking tool',
inputSchema: {
type: "object",
properties: {
thought: {
type: "string",
description: prompts.inputSchema?.thought?.description || 'Your current thinking step and analysis'
},
nextThoughtNeeded: {
type: "boolean",
description: prompts.inputSchema?.nextThoughtNeeded?.description || 'Whether another thought step is needed'
},
thoughtNumber: {
type: "integer",
description: prompts.inputSchema?.thoughtNumber?.description || 'Current step number',
minimum: 1
},
totalThoughts: {
type: "integer",
description: prompts.inputSchema?.totalThoughts?.description || 'Estimated total steps',
minimum: 1
},
commandSelection: {
type: "object",
description: prompts.inputSchema?.commandSelection?.description || 'Optional command or document selection at any step',
properties: {
type: {
type: "string",
enum: ["command", "skip", "skip_reason"],
description: prompts.inputSchema?.commandSelection?.type?.description || 'Type of selection'
},
command: {
type: "string",
description: prompts.inputSchema?.commandSelection?.command?.description || 'Command name'
},
reason: {
type: "string",
description: prompts.inputSchema?.commandSelection?.reason?.description || 'Skip reason'
}
},
required: ["type"]
},
agentSelection: {
type: "object",
description: "Agent selection for SuperGemini Step 3: Agent Persona Selection & Reading",
properties: {
type: {
type: "string",
enum: ["agents", "skip", "skip_reason"],
description: "Type of agent selection"
},
agents: {
type: "array",
items: {
type: "string"
},
description: "Array of agent names to select"
},
reason: {
type: "string",
description: "Skip reason if not selecting agents"
}
},
required: ["type"]
},
isRevision: {
type: "boolean",
description: prompts.inputSchema?.isRevision?.description || 'Whether this revises previous thinking'
},
revisesThought: {
type: "integer",
description: prompts.inputSchema?.revisesThought?.description || 'Which thought is being reconsidered',
minimum: 1
},
branchFromThought: {
type: "integer",
description: prompts.inputSchema?.branchFromThought?.description || 'Branching point thought number',
minimum: 1
},
branchId: {
type: "string",
description: prompts.inputSchema?.branchId?.description || 'Branch identifier'
},
needsMoreThoughts: {
type: "boolean",
description: prompts.inputSchema?.needsMoreThoughts?.description || 'If more thoughts are needed beyond initial estimate'
}
},
required: ["thought", "nextThoughtNeeded", "thoughtNumber", "totalThoughts"]
}
};
const server = new Server({
name: "semantic-prompt",
version: "1.0.0",
}, {
capabilities: {
tools: {},
},
});
// Create server with configuration from prompts file or environment
const serverConfig = {
commandPath: prompts.config?.commandPath || process.env.CHAIN_OF_THOUGHT_COMMAND_PATH || '',
commandExtension: prompts.config?.commandExtension || process.env.CHAIN_OF_THOUGHT_EXTENSION || '.md',
availableCommands: prompts.config?.availableCommands || (process.env.CHAIN_OF_THOUGHT_COMMANDS ? process.env.CHAIN_OF_THOUGHT_COMMANDS.split(',') : []),
requireSelectionByStep: prompts.config?.requireSelectionByStep ?? prompts.config?.requireSelectionFromStep ??
(process.env.CHAIN_OF_THOUGHT_REQUIRE_BY_STEP ? parseInt(process.env.CHAIN_OF_THOUGHT_REQUIRE_BY_STEP, 10) : 2),
enableAutoRead: prompts.config?.enableAutoRead ?? (process.env.CHAIN_OF_THOUGHT_AUTO_READ !== 'false'),
agentsPath: prompts.config?.agentsPath || process.env.CHAIN_OF_THOUGHT_AGENTS_PATH || '~/.gemini/agents/',
agentsExtension: prompts.config?.agentsExtension || process.env.CHAIN_OF_THOUGHT_AGENTS_EXT || '.md',
historyMaxLength: typeof prompts.config?.historyMaxLength === 'number'
? prompts.config?.historyMaxLength
: (process.env.CHAIN_OF_THOUGHT_HISTORY_MAX ? parseInt(process.env.CHAIN_OF_THOUGHT_HISTORY_MAX, 10) : 200),
availableAgents: prompts.config?.availableAgents || (process.env.CHAIN_OF_THOUGHT_AVAILABLE_AGENTS ? process.env.CHAIN_OF_THOUGHT_AVAILABLE_AGENTS.split(',') : undefined),
step3RequiresAgentSelection: prompts.config?.step3RequiresAgentSelection ?? true,
returnAgentDocuments: prompts.config?.returnAgentDocuments ?? true
};
const thinkingServer = new SemanticPromptServer(serverConfig);
// Type assertion to bypass TypeScript errors with MCP SDK
const mcpServer = server;
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [SUPERCLAUDE_THINKING_TOOL],
}));
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === prompts.tool.name) {
// Normalize parameters before processing
const normalizedArgs = thinkingServer.normalizeParameters(request.params.arguments);
return thinkingServer.processThought(normalizedArgs);
}
return {
content: [{
type: "text",
text: prompts.errors.unknownTool.replace('{name}', request.params.name)
}],
isError: true
};
});
async function runServer() {
const transport = new StdioServerTransport();
await mcpServer.connect(transport);
console.error(prompts.messages.serverRunning);
}
runServer().catch((error) => {
console.error(prompts.messages.fatalError.replace('{error}', String(error)));
process.exit(1);
});
//# sourceMappingURL=index.js.map