devcontext
Version:
DevContext is a cutting-edge Model Context Protocol (MCP) server designed to provide developers with continuous, project-centric context awareness.
301 lines (273 loc) • 9.26 kB
JavaScript
/**
* mcpDevContextTools.js
*
* Provides wrapper functions for the MCP DevContext tools
* to ensure proper callback handling and compatibility with MCP SDK.
*/
import { logMessage } from "../utils/logger.js";
// Store conversation ID globally for this session
if (typeof global.lastConversationId === "undefined") {
global.lastConversationId = null;
}
/**
* Creates a wrapped handler for DevContext MCP tools that follows the MCP SDK pattern
*
* @param {Function} handler - The original tool handler function
* @param {string} toolName - The name of the tool for logging purposes
* @returns {Function} A wrapped handler function compatible with MCP SDK
*/
export function createToolHandler(handler, toolName) {
return async (params, context) => {
try {
logMessage("DEBUG", `${toolName} tool handler invoked`, {
paramsKeys: Object.keys(params),
});
// Handle weird parameter structure with signal property
let actualParams = params;
if (
params &&
typeof params === "object" &&
Object.keys(params).length === 1 &&
params.signal &&
Object.keys(params.signal).length === 0
) {
// If we're just getting a signal object with no real params, use defaults
actualParams = {};
logMessage(
"WARN",
`${toolName} received only signal object, using defaults`,
{ params }
);
} else if (params && params.signal && Object.keys(params).length > 1) {
// If params contains signal plus other properties, extract just the other properties
const { signal, ...otherParams } = params;
actualParams = otherParams;
logMessage(
"DEBUG",
`${toolName} extracted parameters from signal object`,
{
extractedParams: Object.keys(actualParams),
}
);
}
// Extract additional parameters from any special formats
const extractedParams = extractParamsFromInput(actualParams);
// Log the extracted parameters for debugging
logMessage("DEBUG", `${toolName} extracted parameters`, {
extractedParams: extractedParams,
});
// Generate default parameters for this specific tool
const defaultParams = createDefaultParamsForTool(toolName);
// Merge extracted parameters with defaults, prioritizing user-provided values
const mergedParams = { ...defaultParams, ...extractedParams };
// Log the merged parameters for debugging
logMessage("DEBUG", `${toolName} merged parameters`, {
mergedParams: mergedParams,
});
// If conversation ID was provided, store it for future use
if (mergedParams.conversationId) {
global.lastConversationId = mergedParams.conversationId;
} else if (global.lastConversationId) {
// Use the last conversation ID if one wasn't provided
mergedParams.conversationId = global.lastConversationId;
logMessage(
"INFO",
`Using last conversation ID: ${global.lastConversationId}`
);
}
// Now call the handler with the properly merged parameters
const result = await handler(mergedParams, context);
return {
content: [
{
type: "text",
text: typeof result === "string" ? result : JSON.stringify(result),
},
],
};
} catch (error) {
logMessage("ERROR", `Error in ${toolName} tool handler`, {
error: error.message,
stack: error.stack,
});
return {
content: [
{
type: "text",
text: JSON.stringify({
error: true,
message: error.message,
details: error.stack,
}),
},
],
};
}
};
}
/**
* Extracts parameters from various input formats
*
* @param {any} input - The input parameters (could be object, string, etc.)
* @returns {Object} Extracted parameters
*/
function extractParamsFromInput(input) {
const extractedParams = {};
try {
// Case 1: Input is already an object
if (input && typeof input === "object") {
// Copy all properties except signal and requestId
Object.keys(input).forEach((key) => {
if (key !== "signal" && key !== "requestId") {
extractedParams[key] = input[key];
}
});
// Special case: If there's a random_string property, try to parse it
if (input.random_string) {
try {
// Try to parse as JSON
const parsedJson = JSON.parse(input.random_string);
Object.assign(extractedParams, parsedJson);
} catch (e) {
// If not JSON, use as-is if it looks like a conversationId
if (
typeof input.random_string === "string" &&
input.random_string.length > 30 &&
input.random_string.includes("-")
) {
extractedParams.conversationId = input.random_string;
}
}
}
// Handle direct parameters from cursor API or user input
if (input.conversationId) {
extractedParams.conversationId = input.conversationId;
}
if (input.initialQuery) {
extractedParams.initialQuery = input.initialQuery;
}
if (input.contextDepth) {
extractedParams.contextDepth = input.contextDepth;
}
if (input.query) {
extractedParams.query = input.query;
}
if (input.name) {
extractedParams.name = input.name;
}
// Process message array if present
if (input.newMessages) {
extractedParams.newMessages = Array.isArray(input.newMessages)
? input.newMessages
: [input.newMessages];
}
// Process code changes if present
if (input.codeChanges) {
extractedParams.codeChanges = Array.isArray(input.codeChanges)
? input.codeChanges
: [input.codeChanges];
}
}
// Case 2: Input is a string
else if (typeof input === "string") {
try {
// Try to parse as JSON
const parsedJson = JSON.parse(input);
Object.assign(extractedParams, parsedJson);
} catch (e) {
// If not JSON, use as conversationId if it looks like one
if (input.length > 30 && input.includes("-")) {
extractedParams.conversationId = input;
}
}
}
} catch (e) {
logMessage("ERROR", `Error extracting params: ${e.message}`);
}
return extractedParams;
}
/**
* Creates default parameters for each tool type
*
* @param {string} toolName - The name of the tool
* @returns {Object} Default parameters for the tool
*/
function createDefaultParamsForTool(toolName) {
switch (toolName) {
case "initialize_conversation_context":
return {
initialQuery: "Starting a new conversation with DevContext",
includeArchitecture: true,
includeRecentConversations: true,
maxCodeContextItems: 5,
maxRecentChanges: 5,
contextDepth: "standard",
};
case "update_conversation_context":
return {
conversationId: global.lastConversationId,
newMessages: [
{
role: "user",
content: "Working with DevContext tools",
},
],
preserveContextOnTopicShift: true,
contextIntegrationLevel: "balanced",
trackIntentTransitions: true,
};
case "retrieve_relevant_context":
return {
conversationId: global.lastConversationId,
query: "DevContext tools and functionality",
constraints: {
includeConversation: true,
crossTopicSearch: false,
},
contextFilters: {
minRelevanceScore: 0.3,
},
weightingStrategy: "balanced",
balanceStrategy: "proportional",
contextBalance: "auto",
};
case "record_milestone_context":
return {
conversationId: global.lastConversationId,
name: "DevContext Tool Milestone",
description: "Milestone recorded during DevContext tools testing",
milestoneCategory: "uncategorized",
assessImpact: true,
};
case "finalize_conversation_context":
return {
conversationId: global.lastConversationId,
clearActiveContext: false,
extractLearnings: true,
promotePatterns: true,
synthesizeRelatedTopics: true,
generateNextSteps: true,
outcome: "completed",
};
default:
return {};
}
}
/**
* Creates a specialized wrapped handler for initialize_conversation_context
*
* @param {Function} handler - The original handler function
* @returns {Function} A wrapped handler function
*/
export function createInitializeContextHandler(handler) {
return createToolHandler(handler, "initialize_conversation_context");
}
/**
* Creates a specialized wrapped handler for finalize_conversation_context
*
* @param {Function} handler - The original handler function
* @returns {Function} A wrapped handler function
*/
export function createFinalizeContextHandler(handler) {
return createToolHandler(handler, "finalize_conversation_context");
}