This is page 3 of 9. Use http://codebase.md/tejpalvirk/contextmanager?page={x} to view the full context.
# Directory Structure
```
├── .gitattributes
├── .gitignore
├── build-all-domains.sh
├── developer
│ ├── .gitattributes
│ ├── developer_advancedcontext.txt
│ ├── developer_buildcontext.txt
│ ├── developer_deletecontext.txt
│ ├── developer_endsession_examples.txt
│ ├── developer_endsession.txt
│ ├── developer_loadcontext.txt
│ ├── developer_startsession.txt
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
├── dist
│ ├── developer
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── main
│ │ ├── descriptions
│ │ │ ├── common_advancedcontext.txt
│ │ │ ├── common_buildcontext.txt
│ │ │ ├── common_deletecontext.txt
│ │ │ ├── common_endsession.txt
│ │ │ ├── common_loadcontext.txt
│ │ │ ├── common_startsession.txt
│ │ │ ├── developer_advancedcontext.txt
│ │ │ ├── developer_buildcontext.txt
│ │ │ ├── developer_deletecontext.txt
│ │ │ ├── developer_endsession_examples.txt
│ │ │ ├── developer_endsession.txt
│ │ │ ├── developer_loadcontext.txt
│ │ │ ├── developer_startsession.txt
│ │ │ ├── project_advancedcontext.txt
│ │ │ ├── project_buildcontext.txt
│ │ │ ├── project_deletecontext.txt
│ │ │ ├── project_endsession_examples.txt
│ │ │ ├── project_endsession.txt
│ │ │ ├── project_loadcontext.txt
│ │ │ ├── project_startsession.txt
│ │ │ ├── qualitativeresearch_advancedcontext.txt
│ │ │ ├── qualitativeresearch_buildcontext.txt
│ │ │ ├── qualitativeresearch_deletecontext.txt
│ │ │ ├── qualitativeresearch_endsession_examples.txt
│ │ │ ├── qualitativeresearch_endsession.txt
│ │ │ ├── qualitativeresearch_loadcontext.txt
│ │ │ ├── qualitativeresearch_startsession.txt
│ │ │ ├── quantitativeresearch_advancedcontext.txt
│ │ │ ├── quantitativeresearch_buildcontext.txt
│ │ │ ├── quantitativeresearch_deletecontext.txt
│ │ │ ├── quantitativeresearch_endsession_examples.txt
│ │ │ ├── quantitativeresearch_endsession.txt
│ │ │ ├── quantitativeresearch_loadcontext.txt
│ │ │ ├── quantitativeresearch_startsession.txt
│ │ │ ├── student_advancedcontext.txt
│ │ │ ├── student_buildcontext.txt
│ │ │ ├── student_deletecontext.txt
│ │ │ ├── student_endsession_examples.txt
│ │ │ ├── student_endsession.txt
│ │ │ ├── student_loadcontext.txt
│ │ │ └── student_startsession.txt
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── mcp.d.ts
│ │ └── mcp.js
│ ├── project
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── qualitativeresearch
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── quantitativeresearch
│ │ ├── index.d.ts
│ │ └── index.js
│ └── student
│ ├── index.d.ts
│ └── index.js
├── main
│ ├── descriptions
│ │ ├── common_advancedcontext.txt
│ │ ├── common_buildcontext.txt
│ │ ├── common_deletecontext.txt
│ │ ├── common_endsession.txt
│ │ ├── common_loadcontext.txt
│ │ ├── common_startsession.txt
│ │ ├── developer_advancedcontext.txt
│ │ ├── developer_buildcontext.txt
│ │ ├── developer_deletecontext.txt
│ │ ├── developer_endsession_examples.txt
│ │ ├── developer_endsession.txt
│ │ ├── developer_loadcontext.txt
│ │ ├── developer_startsession.txt
│ │ ├── project_advancedcontext.txt
│ │ ├── project_buildcontext.txt
│ │ ├── project_deletecontext.txt
│ │ ├── project_endsession_examples.txt
│ │ ├── project_endsession.txt
│ │ ├── project_loadcontext.txt
│ │ ├── project_startsession.txt
│ │ ├── qualitativeresearch_advancedcontext.txt
│ │ ├── qualitativeresearch_buildcontext.txt
│ │ ├── qualitativeresearch_deletecontext.txt
│ │ ├── qualitativeresearch_endsession_examples.txt
│ │ ├── qualitativeresearch_endsession.txt
│ │ ├── qualitativeresearch_loadcontext.txt
│ │ ├── qualitativeresearch_startsession.txt
│ │ ├── quantitativeresearch_advancedcontext.txt
│ │ ├── quantitativeresearch_buildcontext.txt
│ │ ├── quantitativeresearch_deletecontext.txt
│ │ ├── quantitativeresearch_endsession_examples.txt
│ │ ├── quantitativeresearch_endsession.txt
│ │ ├── quantitativeresearch_loadcontext.txt
│ │ ├── quantitativeresearch_startsession.txt
│ │ ├── student_advancedcontext.txt
│ │ ├── student_buildcontext.txt
│ │ ├── student_deletecontext.txt
│ │ ├── student_endsession_examples.txt
│ │ ├── student_endsession.txt
│ │ ├── student_loadcontext.txt
│ │ └── student_startsession.txt
│ ├── index.js
│ ├── index.ts
│ ├── mcp.ts
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── project
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── project_advancedcontext.txt
│ ├── project_buildcontext.txt
│ ├── project_deletecontext.txt
│ ├── project_endsession_examples.txt
│ ├── project_endsession.txt
│ ├── project_loadcontext.txt
│ ├── project_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── qualitativeresearch
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── qualitativeresearch_advancedcontext.txt
│ ├── qualitativeresearch_buildcontext.txt
│ ├── qualitativeresearch_deletecontext.txt
│ ├── qualitativeresearch_endsession_examples.txt
│ ├── qualitativeresearch_endsession.txt
│ ├── qualitativeresearch_loadcontext.txt
│ ├── qualitativeresearch_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── quantitativeresearch
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── quantitativeresearch_advancedcontext.txt
│ ├── quantitativeresearch_buildcontext.txt
│ ├── quantitativeresearch_deletecontext.txt
│ ├── quantitativeresearch_endsession_examples.txt
│ ├── quantitativeresearch_endsession.txt
│ ├── quantitativeresearch_loadcontext.txt
│ ├── quantitativeresearch_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── README.md
├── student
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── README.md
│ ├── student_advancedcontext.txt
│ ├── student_buildcontext.txt
│ ├── student_deletecontext.txt
│ ├── student_endsession_examples.txt
│ ├── student_endsession.txt
│ ├── student_loadcontext.txt
│ ├── student_startsession.txt
│ └── tsconfig.json
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/main/descriptions/quantitativeresearch_endsession.txt:
--------------------------------------------------------------------------------
```
A multi-stage tool for documenting quantitative research sessions, recording statistical analyses, tracking dataset updates, and creating a structured record of research evolution.
When to use this tool:
- Concluding a quantitative research analysis session
- Documenting updates to datasets and variables
- Recording new statistical analyses and test results
- Tracking creation of data visualizations
- Documenting hypothesis test results and conclusions
- Updating statistical model performance information
- Creating a structured record of research activities
- Establishing a formal conclusion to a focused research period
- Building a historical record of project development
- Documenting observations and insights from statistical analysis
- Updating status values for research activities and entities
- Assigning or modifying priority levels for research tasks
- Establishing or modifying sequential relationships between research processes
Key features:
- Provides a structured, multi-stage workflow for research session documentation
- Records dataset updates in the knowledge graph
- Captures new statistical analyses and their results
- Tracks creation of data visualizations and their purposes
- Documents hypothesis test outcomes with statistical significance
- Updates statistical model performance metrics
- Updates project status information
- Maintains session continuity with unique session IDs
- Supports revision of previous stages when needed
- Offers a comprehensive assembly stage that consolidates all session information
- Manages status progression of research activities
- Tracks priority assignments for research tasks
- Documents sequential relationships between research processes
The endsession tool uses a sequential, multi-stage approach with 9 typical stages:
1. Summary Stage: Records basic session information
2. Dataset Updates Stage: Documents changes to datasets
3. New Analyses Stage: Records new statistical tests performed
4. New Visualizations Stage: Documents visualizations created
5. Hypothesis Results Stage: Records outcomes of hypothesis tests
6. Model Updates Stage: Documents changes to statistical models
7. Status Updates Stage: Records changes to entity status values
8. Project Status Stage: Updates the overall project status
9. Assembly Stage: Consolidates all information and finalizes the session record
Parameters explained:
1. sessionId: Required - Unique identifier for the research session
- Obtained from the startsession tool
- Example: "quant_1234567890_abc123"
2. stage: Required - Current stage of the endsession workflow
- Accepts: "summary", "datasetUpdates", "newAnalyses", "newVisualizations", "hypothesisResults", "modelUpdates", "statusUpdates", "projectStatus", or "assembly"
- Each stage has specific data requirements and processing logic
3. stageNumber: Required - The sequence number of the current stage
- Starts at 1 and typically progresses through the stages
- Used to track progress through the session documentation workflow
4. totalStages: Required - Total number of stages planned for this workflow
- Typically 9 for the complete workflow
- Provides context for the progress within the overall process
5. analysis: Optional - Text analysis or observations for the current stage
- Descriptive text explaining the work done in this stage
- Example: "Analyzed multiple regression results and identified significant predictors"
6. stageData: Optional - Stage-specific structured data
- Structure varies by stage type:
* summary: { summary: "Session summary text", duration: "3 hours", project: "ProjectName" }
* datasetUpdates: { datasets: [{ name: "Dataset1", size: "500 rows", variables: "10", status: "active", description: "Dataset description" }] }
* newAnalyses: { analyses: [{ name: "Analysis1", type: "regression", result: "p<0.05", pValue: "0.03", variables: ["var1", "var2"] }] }
* newVisualizations: { visualizations: [{ name: "Viz1", type: "scatter", description: "Correlation visualization", datasetName: "Dataset1" }] }
* hypothesisResults: { hypotheses: [{ name: "H1", status: "completed", evidence: "Statistical significance in regression model", pValue: "0.02" }] }
* modelUpdates: { models: [{ name: "Model1", type: "regression", performance: "R²=0.85", variables: ["var1", "var2"] }] }
* statusUpdates: { statusUpdates: [{ entityName: "Dataset1", newStatus: "completed", note: "Data cleaning and validation complete" }, { entityName: "Model2", newStatus: "active", note: "Model training in progress" }] }
* projectStatus: { projectStatus: "active", projectObservation: "Data analysis phase complete", priorityUpdates: [{ entityName: "AnalysisTask1", priority: "high", note: "Critical for upcoming publication" }], sequenceUpdates: [{ before: "DataCleaning", after: "ModelTraining", note: "Reorganized analysis workflow" }] }
* assembly: No stageData needed - automatically assembled from previous stages
7. nextStageNeeded: Required - Whether additional stages are needed after this one
- Boolean value (true/false)
- Set to false on the final stage to complete the session
8. isRevision: Optional - Whether this is revising a previous stage
- Boolean value (true/false)
- Default: false
9. revisesStage: Optional - If revising, which stage number is being revised
- Required when isRevision is true
- Indicates which previous stage is being updated
Status and Priority Management:
- The statusUpdates stage allows for batch updates to entity status values
- Valid status values include: active, completed, pending, abandoned
- Priority assignments (high, low) can be modified in the projectStatus stage
- Status changes are implemented through has_status relations
- Priority changes are implemented through has_priority relations
- Status and priority changes are tracked to maintain research progress history
Sequential Process Management:
- The projectStatus stage allows for defining or modifying sequential relationships
- The precedes relation is used to establish logical ordering between research processes
- Sequential updates help maintain a coherent research workflow
- Process sequences can be visualized through the loadcontext tool
- Critical research sequences are maintained to ensure methodological integrity
When the endsession workflow completes (assembly stage with nextStageNeeded: false), the tool performs these updates:
1. Dataset Entities: Updates existing datasets or creates new dataset entities with the provided information
2. Statistical Analyses: Creates entities for statistical tests and links them to projects and variables
3. Visualizations: Creates entities for data visualizations and links them to datasets and projects
4. Hypothesis Updates: Updates existing hypotheses or creates new hypothesis entities with test results
5. Model Updates: Updates existing model entities or creates new models with performance metrics
6. Status Updates: Updates entity status values through has_status relations
7. Priority Updates: Updates entity priority values through has_priority relations
8. Sequence Updates: Updates sequential relationships through precedes relations
9. Project Status: Updates the project status, adds an updated timestamp, and records observations
Return information:
- JSON response with the following structure when stages are in progress:
* success: Boolean indicating whether the operation succeeded
* stageCompleted: The stage that was just completed
* nextStageNeeded: Whether more stages are required
* stageResult: The processed result of the current stage
- Formatted markdown text summary when the session is completed, including:
* Session date and project name
* Summary of the session
* Dataset updates
* New statistical analyses
* New visualizations
* Hypothesis test results
* Model updates
* Status changes
* Priority modifications
* Sequential relationship updates
* Project status update
You should:
- Complete all stages in order for comprehensive session documentation
- Provide specific details in each stage for accurate research documentation
- Specify dataset updates with clear size, variable count, and status information
- Include p-values and variable names for statistical analyses
- Connect visualizations to specific datasets when possible
- Document hypothesis test results with evidence and significance levels
- Include performance metrics when updating statistical models
- Update entity status using has_status relations with valid status values (active, completed, pending, abandoned)
- Assign priorities using has_priority relations with valid priority values (high, low)
- Define process sequences using precedes relations to establish research workflows
- Include relevant observations for project status updates
- If making a revision, specify which stage is being revised
- Only mark nextStageNeeded as false on the final assembly stage
- Review the final summary message to confirm all session details were recorded properly
- Use the unique session ID consistently across all stages
```
--------------------------------------------------------------------------------
/quantitativeresearch/quantitativeresearch_endsession.txt:
--------------------------------------------------------------------------------
```
A multi-stage tool for documenting quantitative research sessions, recording statistical analyses, tracking dataset updates, and creating a structured record of research evolution.
When to use this tool:
- Concluding a quantitative research analysis session
- Documenting updates to datasets and variables
- Recording new statistical analyses and test results
- Tracking creation of data visualizations
- Documenting hypothesis test results and conclusions
- Updating statistical model performance information
- Creating a structured record of research activities
- Establishing a formal conclusion to a focused research period
- Building a historical record of project development
- Documenting observations and insights from statistical analysis
- Updating status values for research activities and entities
- Assigning or modifying priority levels for research tasks
- Establishing or modifying sequential relationships between research processes
Key features:
- Provides a structured, multi-stage workflow for research session documentation
- Records dataset updates in the knowledge graph
- Captures new statistical analyses and their results
- Tracks creation of data visualizations and their purposes
- Documents hypothesis test outcomes with statistical significance
- Updates statistical model performance metrics
- Updates project status information
- Maintains session continuity with unique session IDs
- Supports revision of previous stages when needed
- Offers a comprehensive assembly stage that consolidates all session information
- Manages status progression of research activities
- Tracks priority assignments for research tasks
- Documents sequential relationships between research processes
The endsession tool uses a sequential, multi-stage approach with 9 typical stages:
1. Summary Stage: Records basic session information
2. Dataset Updates Stage: Documents changes to datasets
3. New Analyses Stage: Records new statistical tests performed
4. New Visualizations Stage: Documents visualizations created
5. Hypothesis Results Stage: Records outcomes of hypothesis tests
6. Model Updates Stage: Documents changes to statistical models
7. Status Updates Stage: Records changes to entity status values
8. Project Status Stage: Updates the overall project status
9. Assembly Stage: Consolidates all information and finalizes the session record
Parameters explained:
1. sessionId: Required - Unique identifier for the research session
- Obtained from the startsession tool
- Example: "quant_1234567890_abc123"
2. stage: Required - Current stage of the endsession workflow
- Accepts: "summary", "datasetUpdates", "newAnalyses", "newVisualizations", "hypothesisResults", "modelUpdates", "statusUpdates", "projectStatus", or "assembly"
- Each stage has specific data requirements and processing logic
3. stageNumber: Required - The sequence number of the current stage
- Starts at 1 and typically progresses through the stages
- Used to track progress through the session documentation workflow
4. totalStages: Required - Total number of stages planned for this workflow
- Typically 9 for the complete workflow
- Provides context for the progress within the overall process
5. analysis: Optional - Text analysis or observations for the current stage
- Descriptive text explaining the work done in this stage
- Example: "Analyzed multiple regression results and identified significant predictors"
6. stageData: Optional - Stage-specific structured data
- Structure varies by stage type:
* summary: { summary: "Session summary text", duration: "3 hours", project: "ProjectName" }
* datasetUpdates: { datasets: [{ name: "Dataset1", size: "500 rows", variables: "10", status: "active", description: "Dataset description" }] }
* newAnalyses: { analyses: [{ name: "Analysis1", type: "regression", result: "p<0.05", pValue: "0.03", variables: ["var1", "var2"] }] }
* newVisualizations: { visualizations: [{ name: "Viz1", type: "scatter", description: "Correlation visualization", datasetName: "Dataset1" }] }
* hypothesisResults: { hypotheses: [{ name: "H1", status: "completed", evidence: "Statistical significance in regression model", pValue: "0.02" }] }
* modelUpdates: { models: [{ name: "Model1", type: "regression", performance: "R²=0.85", variables: ["var1", "var2"] }] }
* statusUpdates: { statusUpdates: [{ entityName: "Dataset1", newStatus: "completed", note: "Data cleaning and validation complete" }, { entityName: "Model2", newStatus: "active", note: "Model training in progress" }] }
* projectStatus: { projectStatus: "active", projectObservation: "Data analysis phase complete", priorityUpdates: [{ entityName: "AnalysisTask1", priority: "high", note: "Critical for upcoming publication" }], sequenceUpdates: [{ before: "DataCleaning", after: "ModelTraining", note: "Reorganized analysis workflow" }] }
* assembly: No stageData needed - automatically assembled from previous stages
7. nextStageNeeded: Required - Whether additional stages are needed after this one
- Boolean value (true/false)
- Set to false on the final stage to complete the session
8. isRevision: Optional - Whether this is revising a previous stage
- Boolean value (true/false)
- Default: false
9. revisesStage: Optional - If revising, which stage number is being revised
- Required when isRevision is true
- Indicates which previous stage is being updated
Status and Priority Management:
- The statusUpdates stage allows for batch updates to entity status values
- Valid status values include: active, completed, pending, abandoned
- Priority assignments (high, low) can be modified in the projectStatus stage
- Status changes are implemented through has_status relations
- Priority changes are implemented through has_priority relations
- Status and priority changes are tracked to maintain research progress history
Sequential Process Management:
- The projectStatus stage allows for defining or modifying sequential relationships
- The precedes relation is used to establish logical ordering between research processes
- Sequential updates help maintain a coherent research workflow
- Process sequences can be visualized through the loadcontext tool
- Critical research sequences are maintained to ensure methodological integrity
When the endsession workflow completes (assembly stage with nextStageNeeded: false), the tool performs these updates:
1. Dataset Entities: Updates existing datasets or creates new dataset entities with the provided information
2. Statistical Analyses: Creates entities for statistical tests and links them to projects and variables
3. Visualizations: Creates entities for data visualizations and links them to datasets and projects
4. Hypothesis Updates: Updates existing hypotheses or creates new hypothesis entities with test results
5. Model Updates: Updates existing model entities or creates new models with performance metrics
6. Status Updates: Updates entity status values through has_status relations
7. Priority Updates: Updates entity priority values through has_priority relations
8. Sequence Updates: Updates sequential relationships through precedes relations
9. Project Status: Updates the project status, adds an updated timestamp, and records observations
Return information:
- JSON response with the following structure when stages are in progress:
* success: Boolean indicating whether the operation succeeded
* stageCompleted: The stage that was just completed
* nextStageNeeded: Whether more stages are required
* stageResult: The processed result of the current stage
- Formatted markdown text summary when the session is completed, including:
* Session date and project name
* Summary of the session
* Dataset updates
* New statistical analyses
* New visualizations
* Hypothesis test results
* Model updates
* Status changes
* Priority modifications
* Sequential relationship updates
* Project status update
You should:
- Complete all stages in order for comprehensive session documentation
- Provide specific details in each stage for accurate research documentation
- Specify dataset updates with clear size, variable count, and status information
- Include p-values and variable names for statistical analyses
- Connect visualizations to specific datasets when possible
- Document hypothesis test results with evidence and significance levels
- Include performance metrics when updating statistical models
- Update entity status using has_status relations with valid status values (active, completed, pending, abandoned)
- Assign priorities using has_priority relations with valid priority values (high, low)
- Define process sequences using precedes relations to establish research workflows
- Include relevant observations for project status updates
- If making a revision, specify which stage is being revised
- Only mark nextStageNeeded as false on the final assembly stage
- Review the final summary message to confirm all session details were recorded properly
- Use the unique session ID consistently across all stages
```
--------------------------------------------------------------------------------
/main/index.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
import path from "path";
import { fileURLToPath } from "url";
// Get the directory where the contextmanager is located
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Domain client class
class DomainClient {
constructor(domain) {
this.domain = domain;
this.client = null;
this.transport = null;
this.connected = false;
this.process = null;
this.name = domain.name;
}
async connect() {
if (this.connected) {
return true;
}
try {
// Create client
this.client = new Client({
name: `contextmanager-${this.name}-client`,
version: "1.0.0"
}, {
capabilities: {
resources: {},
tools: {},
prompts: {}
}
});
// Connect to domain server
if (this.domain.host && this.domain.port) {
// Connect via SSE
const url = new URL(`http://${this.domain.host}:${this.domain.port}${this.domain.path || '/sse'}`);
this.transport = new SSEClientTransport(url);
}
else if (this.domain.command) {
// Connect via stdio
this.transport = new StdioClientTransport({
command: this.domain.command,
args: this.domain.args || [],
});
}
else {
console.error(`Domain ${this.name} has no connection information`);
return false;
}
await this.client.connect(this.transport);
this.connected = true;
return true;
}
catch (error) {
console.error(`Failed to connect to domain ${this.name}:`, error);
this.connected = false;
return false;
}
}
async disconnect() {
if (!this.connected) {
return;
}
try {
if (this.client) {
// Manually close the transport as there's no official disconnect method
if (this.transport) {
if ('close' in this.transport) {
await this.transport.close();
}
}
}
this.connected = false;
}
catch (error) {
console.error(`Error disconnecting from domain ${this.name}:`, error);
}
}
async callTool(toolRequest) {
if (!this.connected || !this.client) {
const connected = await this.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Not connected to domain ${this.name}` }],
isError: true
};
}
}
try {
if (!this.client) {
throw new Error(`Client for domain ${this.name} is not initialized`);
}
const result = await this.client.callTool(toolRequest);
return result;
}
catch (error) {
console.error(`Error calling tool ${toolRequest.name} on domain ${this.name}:`, error);
return {
content: [{ type: "text", text: `Error calling tool ${toolRequest.name} on domain ${this.name}: ${error}` }],
isError: true
};
}
}
async readResource(resourceRequest) {
if (!this.connected || !this.client) {
const connected = await this.connect();
if (!connected) {
return {
contents: [{
uri: resourceRequest.uri,
text: `Error: Not connected to domain ${this.name}`
}]
};
}
}
try {
if (!this.client) {
throw new Error(`Client for domain ${this.name} is not initialized`);
}
return await this.client.readResource(resourceRequest);
}
catch (error) {
console.error(`Error reading resource ${resourceRequest.uri} from domain ${this.name}:`, error);
return {
contents: [{
uri: resourceRequest.uri,
text: `Error reading resource: ${error}`
}]
};
}
}
}
// In-memory storage
const domains = [
{
name: "developer",
description: "Software development context with entities like projects, components, and tasks",
entityTypes: ["project", "component", "task", "issue", "commit"],
command: "node",
args: [path.resolve(__dirname, "../developer/index.js")]
},
{
name: "project",
description: "Project management context with entities like projects, tasks, and resources",
entityTypes: ["project", "task", "resource", "milestone", "risk"],
command: "node",
args: [path.resolve(__dirname, "../project/index.js")]
},
{
name: "student",
description: "Educational context with entities like courses, assignments, and exams",
entityTypes: ["course", "assignment", "exam", "note", "grade"],
command: "node",
args: [path.resolve(__dirname, "../student/index.js")]
},
{
name: "qualitativeresearch",
description: "Qualitative research context with entities like studies, participants, and interviews",
entityTypes: ["study", "participant", "interview", "code", "theme"],
command: "node",
args: [path.resolve(__dirname, "../qualitativeresearch/index.js")]
},
{
name: "quantitativeresearch",
description: "Quantitative research context with entities like datasets, variables, and analyses",
entityTypes: ["dataset", "variable", "analysis", "model", "result"],
command: "node",
args: [path.resolve(__dirname, "../quantitativeresearch/index.js")]
}
];
// Domain clients
const domainClients = {};
// Initialize domain clients
for (const domain of domains) {
domainClients[domain.name] = new DomainClient(domain);
}
// In-memory flow management
const flows = [];
let activeDomain = null;
let flowCounter = 0;
// Create an MCP server
const server = new McpServer({
name: "Context Manager",
version: "1.0.0"
});
// Domain management tools
server.tool("setActiveDomain", { domain: z.string() }, async ({ domain }) => {
const foundDomain = domains.find(d => d.name.toLowerCase() === domain.toLowerCase());
if (!foundDomain) {
return {
content: [{ type: "text", text: `Error: Domain '${domain}' not found. Available domains: ${domains.map(d => d.name).join(", ")}` }],
isError: true
};
}
// Connect to the domain server
const domainClient = domainClients[foundDomain.name];
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${domain}'` }],
isError: true
};
}
activeDomain = foundDomain.name;
return {
content: [{ type: "text", text: `Active domain set to: ${activeDomain}` }]
};
});
// Flow management tools
server.tool("startsession", {
domain: z.string()
}, async ({ domain }) => {
const foundDomain = domains.find(d => d.name.toLowerCase() === domain.toLowerCase());
if (!foundDomain) {
return {
content: [{ type: "text", text: `Error: Domain '${domain}' not found. Available domains: ${domains.map(d => d.name).join(", ")}` }],
isError: true
};
}
// Connect to the domain server
const domainClient = domainClients[foundDomain.name];
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${domain}'` }],
isError: true
};
}
activeDomain = foundDomain.name;
flowCounter++;
// Forward the startsession call to the domain server with a domain-specific session identifier
try {
const result = await domainClient.callTool({
name: "startsession",
arguments: {}
});
const lastWord = result.content[0].text.split(' ').pop();
// Create a contextmanager flow ID from domain session ID (last word)
const flowId = `flow_${lastWord}`;
flows.push({
id: flowId,
domain: activeDomain,
active: true,
createdAt: Date.now()
});
return {
content: [
{ type: "text", text: `${result.content[0].text}` }
],
};
}
catch (error) {
console.error(`Error starting session for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error starting session for domain ${activeDomain}: ${error}` }],
isError: true
};
}
});
server.tool("endsession", {
sessionId: z.string(),
stage: z.string(),
stageNumber: z.number(),
totalStages: z.number(),
nextStageNeeded: z.boolean(),
analysis: z.string().optional(),
isRevision: z.boolean().optional(),
revisesStage: z.number().optional(),
stageData: z.any().optional()
}, async ({ sessionId, stage, stageNumber, totalStages, nextStageNeeded, analysis, isRevision, revisesStage, stageData }) => {
const flowId = `flow_${sessionId}`;
const flowIndex = flows.findIndex(s => s.id === flowId);
if (flowIndex === -1) {
return {
content: [{ type: "text", text: `Error: Context Manager flow with ID '${flowId}' not found.` }],
isError: true
};
}
const flow = flows[flowIndex];
const domainName = flow.domain;
const domainClient = domainClients[domainName];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${domainName}'` }],
isError: true
};
}
}
// Forward the endsession call to the domain server
try {
const result = await domainClient.callTool({
name: "endsession",
arguments: {
sessionId: sessionId,
stage,
stageNumber,
totalStages,
nextStageNeeded,
analysis,
isRevision,
revisesStage,
stageData
}
});
if (!nextStageNeeded) {
flows[flowIndex].active = false;
return {
content: [{ type: "text", text: `${result.content[0].text}` }]
};
}
return {
content: [{ type: "text", text: `${result.content[0].text}` }]
};
}
catch (error) {
console.error(`Error ending session for domain ${domainName}:`, error);
return {
content: [{ type: "text", text: `Error ending session for domain ${domainName}: ${error}` }],
isError: true
};
}
});
// Context management tools
server.tool("buildcontext", {
type: z.enum(["entities", "relations", "observations"]),
data: z.any().optional()
}, async ({ type, data }) => {
if (!activeDomain) {
return {
content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain tool first." }],
isError: true
};
}
const domainClient = domainClients[activeDomain];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
isError: true
};
}
}
// Forward the buildcontext call to the domain server
try {
const result = await domainClient.callTool({
name: "buildcontext",
arguments: {
type,
data
}
});
return result;
}
catch (error) {
console.error(`Error building context for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error building context for domain ${activeDomain}: ${error}` }],
isError: true
};
}
});
server.tool("deletecontext", {
type: z.enum(["entities", "relations", "observations"]),
data: z.any().optional()
}, async ({ type, data }) => {
if (!activeDomain) {
return {
content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain tool first." }],
isError: true
};
}
const domainClient = domainClients[activeDomain];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
isError: true
};
}
}
// Forward the deletecontext call to the domain server
try {
const result = await domainClient.callTool({
name: "deletecontext",
arguments: {
type,
data
}
});
return result;
}
catch (error) {
console.error(`Error deleting context for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error deleting context for domain ${activeDomain}: ${error}` }],
isError: true
};
}
});
server.tool("loadcontext", {
entityName: z.string(),
entityType: z.string().optional(),
sessionId: z.string().optional()
}, async ({ entityName, entityType, sessionId }) => {
if (!activeDomain) {
return {
content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain tool first." }],
isError: true
};
}
const flowId = `flow_${sessionId}`;
// Find active contextmanager flow or use provided sessionId
let targetFlow;
if (sessionId) {
targetFlow = flows.find(s => s.id === flowId);
}
else {
targetFlow = flows.find(s => s.domain === activeDomain && s.active);
}
if (!targetFlow) {
return {
content: [{ type: "text", text: "Error: No active Context Manager flow found. Start a flow first." }],
isError: true
};
}
const domainClient = domainClients[activeDomain];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
isError: true
};
}
}
// Update contextmanager flow with entity details
targetFlow.entityName = entityName;
targetFlow.entityType = entityType || "unknown";
// Forward the loadcontext call to the domain server
try {
const result = await domainClient.callTool({
name: "loadcontext",
arguments: {
entityName,
entityType,
sessionId: sessionId
}
});
return {
content: [{
type: "text",
text: `${result.content[0].text}`
}]
};
}
catch (error) {
console.error(`Error loading context for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error loading context for domain ${activeDomain}: ${error}` }],
isError: true
};
}
});
server.tool("advancedcontext", {
type: z.enum(["graph", "search", "nodes", "related", "decisions", "milestone"]),
params: z.any().optional()
}, async ({ type, params }) => {
if (!activeDomain) {
return {
content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain tool first." }],
isError: true
};
}
const domainClient = domainClients[activeDomain];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
isError: true
};
}
}
// Forward the advancedcontext call to the domain server
try {
const result = await domainClient.callTool({
name: "advancedcontext",
arguments: {
type,
params
}
});
return result;
}
catch (error) {
console.error(`Error performing advanced context operation for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error performing advanced context operation for domain ${activeDomain}: ${error}` }],
isError: true
};
}
});
// // List all entities
// server.tool(
// "listAllEntities",
// {
// domain: z.string().optional()
// },
// async () => {
// if (!activeDomain) {
// return {
// content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain tool first." }],
// isError: true
// };
// }
// const domainClient = domainClients[activeDomain];
// if (!domainClient || !domainClient.connected) {
// const connected = await domainClient.connect();
// if (!connected) {
// return {
// content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
// isError: true
// };
// }
// }
// // Forward the listAllEntities call to the domain server
// try {
// const result = await domainClient.callTool({
// name: "listAllEntities",
// arguments: {}
// });
// return result;
// } catch (error) {
// // If the domain server doesn't support listAllEntities, fall back to our domain registry
// const domain = domains.find(d => d.name === activeDomain);
// if (!domain) {
// return {
// content: [{ type: "text", text: `Error: Domain '${activeDomain}' not found.` }],
// isError: true
// };
// }
// return {
// content: [{
// type: "text",
// text: `Available entity types in ${activeDomain} domain: ${domain.entityTypes.join(", ")}`
// }]
// };
// }
// }
// );
// Domain discovery resources
server.resource("domains", "domains://list", async (uri) => ({
contents: [{
uri: uri.href,
text: JSON.stringify(domains, null, 2)
}]
}));
// Cross-domain functionality
server.tool("relateCrossDomain", {
fromDomain: z.string(),
fromEntity: z.string(),
toDomain: z.string(),
toEntity: z.string(),
relationType: z.string()
}, async ({ fromDomain, fromEntity, toDomain, toEntity, relationType }) => {
// Validate domains
const fromDomainInfo = domains.find(d => d.name.toLowerCase() === fromDomain.toLowerCase());
const toDomainInfo = domains.find(d => d.name.toLowerCase() === toDomain.toLowerCase());
if (!fromDomainInfo) {
return {
content: [{ type: "text", text: `Error: Source domain '${fromDomain}' not found.` }],
isError: true
};
}
if (!toDomainInfo) {
return {
content: [{ type: "text", text: `Error: Target domain '${toDomain}' not found.` }],
isError: true
};
}
// Create cross-domain relation by adding observations to both entities
try {
// Connect to source domain
const fromDomainClient = domainClients[fromDomainInfo.name];
if (!fromDomainClient || !fromDomainClient.connected) {
const connected = await fromDomainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${fromDomain}'` }],
isError: true
};
}
}
// Connect to target domain
const toDomainClient = domainClients[toDomainInfo.name];
if (!toDomainClient || !toDomainClient.connected) {
const connected = await toDomainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${toDomain}'` }],
isError: true
};
}
}
// Add observation to source entity about relation to target entity
await fromDomainClient.callTool({
name: "buildcontext",
arguments: {
type: "observations",
data: {
observations: [
{
entityName: fromEntity,
contents: [`Related to ${toEntity} (${toDomainInfo.name} domain) via ${relationType}`]
}
]
}
}
});
// Add observation to target entity about relation from source entity
await toDomainClient.callTool({
name: "buildcontext",
arguments: {
type: "observations",
data: {
observations: [
{
entityName: toEntity,
contents: [`Related from ${fromEntity} (${fromDomainInfo.name} domain) via ${relationType}`]
}
]
}
}
});
return {
content: [{
type: "text",
text: `Created cross-domain relation: ${fromEntity} (${fromDomain}) --[${relationType}]--> ${toEntity} (${toDomain})`
}]
};
}
catch (error) {
console.error(`Error creating cross-domain relation:`, error);
return {
content: [{ type: "text", text: `Error creating cross-domain relation: ${error}` }],
isError: true
};
}
});
// Main function to start the server
async function main() {
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
}
// Execute the main function
main().catch(error => {
console.error("Error in contextmanager:", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/main/mcp.ts:
--------------------------------------------------------------------------------
```typescript
import { Server, ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";
import { zodToJsonSchema } from "zod-to-json-schema";
import {
z,
ZodRawShape,
ZodObject,
ZodString,
AnyZodObject,
ZodTypeAny,
ZodType,
ZodTypeDef,
ZodOptional,
} from "zod";
import {
Implementation,
Tool,
ListToolsResult,
CallToolResult,
McpError,
ErrorCode,
CompleteRequest,
CompleteResult,
PromptReference,
ResourceReference,
Resource,
ListResourcesResult,
ListResourceTemplatesRequestSchema,
ReadResourceRequestSchema,
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
CompleteRequestSchema,
ListPromptsResult,
Prompt,
PromptArgument,
GetPromptResult,
ReadResourceResult,
} from "@modelcontextprotocol/sdk/types.js";
import { Completable, CompletableDef } from "@modelcontextprotocol/sdk/server/completable.js";
import { UriTemplate, Variables } from "@modelcontextprotocol/sdk/shared/uriTemplate.js";
import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
/**
* High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
* For advanced usage (like sending notifications or setting custom request handlers), use the underlying
* Server instance available via the `server` property.
*/
export class McpServer {
/**
* The underlying Server instance, useful for advanced operations like sending notifications.
*/
public readonly server: Server;
private _registeredResources: { [uri: string]: RegisteredResource } = {};
private _registeredResourceTemplates: {
[name: string]: RegisteredResourceTemplate;
} = {};
private _registeredTools: { [name: string]: RegisteredTool } = {};
private _registeredPrompts: { [name: string]: RegisteredPrompt } = {};
constructor(serverInfo: Implementation, options?: ServerOptions) {
this.server = new Server(serverInfo, options);
}
/**
* Attaches to the given transport, starts it, and starts listening for messages.
*
* The `server` object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
*/
async connect(transport: Transport): Promise<void> {
return await this.server.connect(transport);
}
/**
* Closes the connection.
*/
async close(): Promise<void> {
await this.server.close();
}
private _toolHandlersInitialized = false;
private setToolRequestHandlers() {
if (this._toolHandlersInitialized) {
return;
}
this.server.assertCanSetRequestHandler(
ListToolsRequestSchema.shape.method.value,
);
this.server.assertCanSetRequestHandler(
CallToolRequestSchema.shape.method.value,
);
this.server.registerCapabilities({
tools: {},
});
this.server.setRequestHandler(
ListToolsRequestSchema,
(): ListToolsResult => ({
tools: Object.entries(this._registeredTools).map(
([name, tool]): Tool => {
return {
name,
description: tool.description,
inputSchema: tool.inputSchema
? (zodToJsonSchema(tool.inputSchema, {
strictUnions: true,
}) as Tool["inputSchema"])
: EMPTY_OBJECT_JSON_SCHEMA,
};
},
),
}),
);
this.server.setRequestHandler(
CallToolRequestSchema,
async (request, extra): Promise<CallToolResult> => {
const tool = this._registeredTools[request.params.name];
if (!tool) {
throw new McpError(
ErrorCode.InvalidParams,
`Tool ${request.params.name} not found`,
);
}
if (tool.inputSchema) {
const parseResult = await tool.inputSchema.safeParseAsync(
request.params.arguments,
);
if (!parseResult.success) {
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for tool ${request.params.name}: ${parseResult.error.message}`,
);
}
const args = parseResult.data;
const cb = tool.callback as ToolCallback<ZodRawShape>;
try {
return await Promise.resolve(cb(args, extra));
} catch (error) {
return {
content: [
{
type: "text",
text: error instanceof Error ? error.message : String(error),
},
],
isError: true,
};
}
} else {
const cb = tool.callback as ToolCallback<undefined>;
try {
return await Promise.resolve(cb(extra));
} catch (error) {
return {
content: [
{
type: "text",
text: error instanceof Error ? error.message : String(error),
},
],
isError: true,
};
}
}
},
);
this._toolHandlersInitialized = true;
}
private _completionHandlerInitialized = false;
private setCompletionRequestHandler() {
if (this._completionHandlerInitialized) {
return;
}
this.server.assertCanSetRequestHandler(
CompleteRequestSchema.shape.method.value,
);
this.server.setRequestHandler(
CompleteRequestSchema,
async (request): Promise<CompleteResult> => {
switch (request.params.ref.type) {
case "ref/prompt":
return this.handlePromptCompletion(request, request.params.ref);
case "ref/resource":
return this.handleResourceCompletion(request, request.params.ref);
default:
throw new McpError(
ErrorCode.InvalidParams,
`Invalid completion reference: ${request.params.ref}`,
);
}
},
);
this._completionHandlerInitialized = true;
}
private async handlePromptCompletion(
request: CompleteRequest,
ref: PromptReference,
): Promise<CompleteResult> {
const prompt = this._registeredPrompts[ref.name];
if (!prompt) {
throw new McpError(
ErrorCode.InvalidParams,
`Prompt ${request.params.ref.name} not found`,
);
}
if (!prompt.argsSchema) {
return EMPTY_COMPLETION_RESULT;
}
const field = prompt.argsSchema.shape[request.params.argument.name];
if (!(field instanceof Completable)) {
return EMPTY_COMPLETION_RESULT;
}
const def: CompletableDef<ZodString> = field._def;
const suggestions = await def.complete(request.params.argument.value);
return createCompletionResult(suggestions);
}
private async handleResourceCompletion(
request: CompleteRequest,
ref: ResourceReference,
): Promise<CompleteResult> {
const template = Object.values(this._registeredResourceTemplates).find(
(t) => t.resourceTemplate.uriTemplate.toString() === ref.uri,
);
if (!template) {
if (this._registeredResources[ref.uri]) {
// Attempting to autocomplete a fixed resource URI is not an error in the spec (but probably should be).
return EMPTY_COMPLETION_RESULT;
}
throw new McpError(
ErrorCode.InvalidParams,
`Resource template ${request.params.ref.uri} not found`,
);
}
const completer = template.resourceTemplate.completeCallback(
request.params.argument.name,
);
if (!completer) {
return EMPTY_COMPLETION_RESULT;
}
const suggestions = await completer(request.params.argument.value);
return createCompletionResult(suggestions);
}
private _resourceHandlersInitialized = false;
private setResourceRequestHandlers() {
if (this._resourceHandlersInitialized) {
return;
}
this.server.assertCanSetRequestHandler(
ListResourcesRequestSchema.shape.method.value,
);
this.server.assertCanSetRequestHandler(
ListResourceTemplatesRequestSchema.shape.method.value,
);
this.server.assertCanSetRequestHandler(
ReadResourceRequestSchema.shape.method.value,
);
this.server.registerCapabilities({
resources: {},
});
this.server.setRequestHandler(
ListResourcesRequestSchema,
async (request, extra) => {
const resources = Object.entries(this._registeredResources).map(
([uri, resource]) => ({
uri,
name: resource.name,
...resource.metadata,
}),
);
const templateResources: Resource[] = [];
for (const template of Object.values(
this._registeredResourceTemplates,
)) {
if (!template.resourceTemplate.listCallback) {
continue;
}
const result = await template.resourceTemplate.listCallback(extra);
for (const resource of result.resources) {
templateResources.push({
...resource,
...template.metadata,
});
}
}
return { resources: [...resources, ...templateResources] };
},
);
this.server.setRequestHandler(
ListResourceTemplatesRequestSchema,
async () => {
const resourceTemplates = Object.entries(
this._registeredResourceTemplates,
).map(([name, template]) => ({
name,
uriTemplate: template.resourceTemplate.uriTemplate.toString(),
...template.metadata,
}));
return { resourceTemplates };
},
);
this.server.setRequestHandler(
ReadResourceRequestSchema,
async (request, extra) => {
const uri = new URL(request.params.uri);
// First check for exact resource match
const resource = this._registeredResources[uri.toString()];
if (resource) {
return resource.readCallback(uri, extra);
}
// Then check templates
for (const template of Object.values(
this._registeredResourceTemplates,
)) {
const variables = template.resourceTemplate.uriTemplate.match(
uri.toString(),
);
if (variables) {
return template.readCallback(uri, variables, extra);
}
}
throw new McpError(
ErrorCode.InvalidParams,
`Resource ${uri} not found`,
);
},
);
this.setCompletionRequestHandler();
this._resourceHandlersInitialized = true;
}
private _promptHandlersInitialized = false;
private setPromptRequestHandlers() {
if (this._promptHandlersInitialized) {
return;
}
this.server.assertCanSetRequestHandler(
ListPromptsRequestSchema.shape.method.value,
);
this.server.assertCanSetRequestHandler(
GetPromptRequestSchema.shape.method.value,
);
this.server.registerCapabilities({
prompts: {},
});
this.server.setRequestHandler(
ListPromptsRequestSchema,
(): ListPromptsResult => ({
prompts: Object.entries(this._registeredPrompts).map(
([name, prompt]): Prompt => {
return {
name,
description: prompt.description,
arguments: prompt.argsSchema
? promptArgumentsFromSchema(prompt.argsSchema)
: undefined,
};
},
),
}),
);
this.server.setRequestHandler(
GetPromptRequestSchema,
async (request, extra): Promise<GetPromptResult> => {
const prompt = this._registeredPrompts[request.params.name];
if (!prompt) {
throw new McpError(
ErrorCode.InvalidParams,
`Prompt ${request.params.name} not found`,
);
}
if (prompt.argsSchema) {
const parseResult = await prompt.argsSchema.safeParseAsync(
request.params.arguments,
);
if (!parseResult.success) {
throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for prompt ${request.params.name}: ${parseResult.error.message}`,
);
}
const args = parseResult.data;
const cb = prompt.callback as PromptCallback<PromptArgsRawShape>;
return await Promise.resolve(cb(args, extra));
} else {
const cb = prompt.callback as PromptCallback<undefined>;
return await Promise.resolve(cb(extra));
}
},
);
this.setCompletionRequestHandler();
this._promptHandlersInitialized = true;
}
/**
* Registers a resource `name` at a fixed URI, which will use the given callback to respond to read requests.
*/
resource(name: string, uri: string, readCallback: ReadResourceCallback): void;
/**
* Registers a resource `name` at a fixed URI with metadata, which will use the given callback to respond to read requests.
*/
resource(
name: string,
uri: string,
metadata: ResourceMetadata,
readCallback: ReadResourceCallback,
): void;
/**
* Registers a resource `name` with a template pattern, which will use the given callback to respond to read requests.
*/
resource(
name: string,
template: ResourceTemplate,
readCallback: ReadResourceTemplateCallback,
): void;
/**
* Registers a resource `name` with a template pattern and metadata, which will use the given callback to respond to read requests.
*/
resource(
name: string,
template: ResourceTemplate,
metadata: ResourceMetadata,
readCallback: ReadResourceTemplateCallback,
): void;
resource(
name: string,
uriOrTemplate: string | ResourceTemplate,
...rest: unknown[]
): void {
let metadata: ResourceMetadata | undefined;
if (typeof rest[0] === "object") {
metadata = rest.shift() as ResourceMetadata;
}
const readCallback = rest[0] as
| ReadResourceCallback
| ReadResourceTemplateCallback;
if (typeof uriOrTemplate === "string") {
if (this._registeredResources[uriOrTemplate]) {
throw new Error(`Resource ${uriOrTemplate} is already registered`);
}
this._registeredResources[uriOrTemplate] = {
name,
metadata,
readCallback: readCallback as ReadResourceCallback,
};
} else {
if (this._registeredResourceTemplates[name]) {
throw new Error(`Resource template ${name} is already registered`);
}
this._registeredResourceTemplates[name] = {
resourceTemplate: uriOrTemplate,
metadata,
readCallback: readCallback as ReadResourceTemplateCallback,
};
}
this.setResourceRequestHandlers();
}
/**
* Registers a zero-argument tool `name`, which will run the given function when the client calls it.
*/
tool(name: string, cb: ToolCallback): void;
/**
* Registers a zero-argument tool `name` (with a description) which will run the given function when the client calls it.
*/
tool(name: string, description: string, cb: ToolCallback): void;
/**
* Registers a tool `name` accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.
*/
tool<Args extends ZodRawShape>(
name: string,
paramsSchema: Args,
cb: ToolCallback<Args>,
): void;
/**
* Registers a tool `name` (with a description) accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.
*/
tool<Args extends ZodRawShape>(
name: string,
description: string,
paramsSchema: Args,
cb: ToolCallback<Args>,
): void;
tool(name: string, ...rest: unknown[]): void {
if (this._registeredTools[name]) {
throw new Error(`Tool ${name} is already registered`);
}
let description: string | undefined;
if (typeof rest[0] === "string") {
description = rest.shift() as string;
}
let paramsSchema: ZodRawShape | undefined;
if (rest.length > 1) {
paramsSchema = rest.shift() as ZodRawShape;
}
const cb = rest[0] as ToolCallback<ZodRawShape | undefined>;
this._registeredTools[name] = {
description,
inputSchema:
paramsSchema === undefined ? undefined : z.object(paramsSchema),
callback: cb,
};
this.setToolRequestHandlers();
}
/**
* Removes a registered tool by name.
* @param name - The name of the tool to remove.
* @returns boolean - Returns true if the tool was removed, false if the tool was not found.
*/
removeTool(name: string): boolean {
if (this._registeredTools[name]) {
delete this._registeredTools[name];
return true;
}
return false;
}
/**
* Registers a zero-argument prompt `name`, which will run the given function when the client calls it.
*/
prompt(name: string, cb: PromptCallback): void;
/**
* Registers a zero-argument prompt `name` (with a description) which will run the given function when the client calls it.
*/
prompt(name: string, description: string, cb: PromptCallback): void;
/**
* Registers a prompt `name` accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.
*/
prompt<Args extends PromptArgsRawShape>(
name: string,
argsSchema: Args,
cb: PromptCallback<Args>,
): void;
/**
* Registers a prompt `name` (with a description) accepting the given arguments, which must be an object containing named properties associated with Zod schemas. When the client calls it, the function will be run with the parsed and validated arguments.
*/
prompt<Args extends PromptArgsRawShape>(
name: string,
description: string,
argsSchema: Args,
cb: PromptCallback<Args>,
): void;
prompt(name: string, ...rest: unknown[]): void {
if (this._registeredPrompts[name]) {
throw new Error(`Prompt ${name} is already registered`);
}
let description: string | undefined;
if (typeof rest[0] === "string") {
description = rest.shift() as string;
}
let argsSchema: PromptArgsRawShape | undefined;
if (rest.length > 1) {
argsSchema = rest.shift() as PromptArgsRawShape;
}
const cb = rest[0] as PromptCallback<PromptArgsRawShape | undefined>;
this._registeredPrompts[name] = {
description,
argsSchema: argsSchema === undefined ? undefined : z.object(argsSchema),
callback: cb,
};
this.setPromptRequestHandlers();
}
}
/**
* A callback to complete one variable within a resource template's URI template.
*/
export type CompleteResourceTemplateCallback = (
value: string,
) => string[] | Promise<string[]>;
/**
* A resource template combines a URI pattern with optional functionality to enumerate
* all resources matching that pattern.
*/
export class ResourceTemplate {
private _uriTemplate: UriTemplate;
constructor(
uriTemplate: string | UriTemplate,
private _callbacks: {
/**
* A callback to list all resources matching this template. This is required to specified, even if `undefined`, to avoid accidentally forgetting resource listing.
*/
list: ListResourcesCallback | undefined;
/**
* An optional callback to autocomplete variables within the URI template. Useful for clients and users to discover possible values.
*/
complete?: {
[variable: string]: CompleteResourceTemplateCallback;
};
},
) {
this._uriTemplate =
typeof uriTemplate === "string"
? new UriTemplate(uriTemplate)
: uriTemplate;
}
/**
* Gets the URI template pattern.
*/
get uriTemplate(): UriTemplate {
return this._uriTemplate;
}
/**
* Gets the list callback, if one was provided.
*/
get listCallback(): ListResourcesCallback | undefined {
return this._callbacks.list;
}
/**
* Gets the callback for completing a specific URI template variable, if one was provided.
*/
completeCallback(
variable: string,
): CompleteResourceTemplateCallback | undefined {
return this._callbacks.complete?.[variable];
}
}
/**
* Callback for a tool handler registered with Server.tool().
*
* Parameters will include tool arguments, if applicable, as well as other request handler context.
*/
export type ToolCallback<Args extends undefined | ZodRawShape = undefined> =
Args extends ZodRawShape
? (
args: z.objectOutputType<Args, ZodTypeAny>,
extra: RequestHandlerExtra,
) => CallToolResult | Promise<CallToolResult>
: (extra: RequestHandlerExtra) => CallToolResult | Promise<CallToolResult>;
type RegisteredTool = {
description?: string;
inputSchema?: AnyZodObject;
callback: ToolCallback<undefined | ZodRawShape>;
};
const EMPTY_OBJECT_JSON_SCHEMA = {
type: "object" as const,
};
/**
* Additional, optional information for annotating a resource.
*/
export type ResourceMetadata = Omit<Resource, "uri" | "name">;
/**
* Callback to list all resources matching a given template.
*/
export type ListResourcesCallback = (
extra: RequestHandlerExtra,
) => ListResourcesResult | Promise<ListResourcesResult>;
/**
* Callback to read a resource at a given URI.
*/
export type ReadResourceCallback = (
uri: URL,
extra: RequestHandlerExtra,
) => ReadResourceResult | Promise<ReadResourceResult>;
type RegisteredResource = {
name: string;
metadata?: ResourceMetadata;
readCallback: ReadResourceCallback;
};
/**
* Callback to read a resource at a given URI, following a filled-in URI template.
*/
export type ReadResourceTemplateCallback = (
uri: URL,
variables: Variables,
extra: RequestHandlerExtra,
) => ReadResourceResult | Promise<ReadResourceResult>;
type RegisteredResourceTemplate = {
resourceTemplate: ResourceTemplate;
metadata?: ResourceMetadata;
readCallback: ReadResourceTemplateCallback;
};
type PromptArgsRawShape = {
[k: string]:
| ZodType<string, ZodTypeDef, string>
| ZodOptional<ZodType<string, ZodTypeDef, string>>;
};
export type PromptCallback<
Args extends undefined | PromptArgsRawShape = undefined,
> = Args extends PromptArgsRawShape
? (
args: z.objectOutputType<Args, ZodTypeAny>,
extra: RequestHandlerExtra,
) => GetPromptResult | Promise<GetPromptResult>
: (extra: RequestHandlerExtra) => GetPromptResult | Promise<GetPromptResult>;
type RegisteredPrompt = {
description?: string;
argsSchema?: ZodObject<PromptArgsRawShape>;
callback: PromptCallback<undefined | PromptArgsRawShape>;
};
function promptArgumentsFromSchema(
schema: ZodObject<PromptArgsRawShape>,
): PromptArgument[] {
return Object.entries(schema.shape).map(
([name, field]): PromptArgument => ({
name,
description: field.description,
required: !field.isOptional(),
}),
);
}
function createCompletionResult(suggestions: string[]): CompleteResult {
return {
completion: {
values: suggestions.slice(0, 100),
total: suggestions.length,
hasMore: suggestions.length > 100,
},
};
}
const EMPTY_COMPLETION_RESULT: CompleteResult = {
completion: {
values: [],
hasMore: false,
},
};
```
--------------------------------------------------------------------------------
/main/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { McpServer } from "./mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
import path from "path";
import { fileURLToPath } from "url";
import { readFileSync, existsSync } from "fs";
// Get the directory where the contextmanager is located
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Domain interfaces
interface DomainInfo {
name: string;
description: string;
entityTypes: string[];
host?: string;
port?: number;
path?: string;
command?: string;
args?: string[];
}
interface FlowInfo {
id: string;
domain: string;
entityName?: string;
entityType?: string;
active: boolean;
createdAt: number;
}
// Domain client class
class DomainClient {
public name: string;
public client: Client | null = null;
public transport: SSEClientTransport | StdioClientTransport | null = null;
public connected: boolean = false;
private process: any = null;
constructor(public domain: DomainInfo) {
this.name = domain.name;
}
async connect(): Promise<boolean> {
if (this.connected) {
return true;
}
try {
// Create client
this.client = new Client(
{
name: `contextmanager-${this.name}-client`,
version: "1.0.0"
},
{
capabilities: {
resources: {},
tools: {},
prompts: {}
}
}
);
// Connect to domain server
if (this.domain.host && this.domain.port) {
// Connect via SSE
const url = new URL(`http://${this.domain.host}:${this.domain.port}${this.domain.path || '/sse'}`);
this.transport = new SSEClientTransport(url);
} else if (this.domain.command) {
// Connect via stdio
this.transport = new StdioClientTransport({
command: this.domain.command,
args: this.domain.args || [],
});
} else {
console.error(`Domain ${this.name} has no connection information`);
return false;
}
await this.client.connect(this.transport);
this.connected = true;
return true;
} catch (error) {
console.error(`Failed to connect to domain ${this.name}:`, error);
this.connected = false;
return false;
}
}
async disconnect(): Promise<void> {
if (!this.connected) {
return;
}
try {
if (this.client) {
// Manually close the transport as there's no official disconnect method
if (this.transport) {
if ('close' in this.transport) {
await (this.transport as any).close();
}
}
}
this.connected = false;
} catch (error) {
console.error(`Error disconnecting from domain ${this.name}:`, error);
}
}
async callTool(toolRequest: { name: string; arguments: any }): Promise<any> {
if (!this.connected || !this.client) {
const connected = await this.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Not connected to domain ${this.name}` }],
isError: true
};
}
}
try {
if (!this.client) {
throw new Error(`Client for domain ${this.name} is not initialized`);
}
const result = await this.client.callTool(toolRequest);
return result;
} catch (error) {
console.error(`Error calling tool ${toolRequest.name} on domain ${this.name}:`, error);
return {
content: [{ type: "text", text: `Error calling tool ${toolRequest.name} on domain ${this.name}: ${error}` }],
isError: true
};
}
}
async readResource(resourceRequest: { uri: string }): Promise<any> {
if (!this.connected || !this.client) {
const connected = await this.connect();
if (!connected) {
return {
contents: [{
uri: resourceRequest.uri,
text: `Error: Not connected to domain ${this.name}`
}]
};
}
}
try {
if (!this.client) {
throw new Error(`Client for domain ${this.name} is not initialized`);
}
return await this.client.readResource(resourceRequest);
} catch (error) {
console.error(`Error reading resource ${resourceRequest.uri} from domain ${this.name}:`, error);
return {
contents: [{
uri: resourceRequest.uri,
text: `Error reading resource: ${error}`
}]
};
}
}
}
// In-memory storage
const domains: DomainInfo[] = [
{
name: "developer",
description: "Software development context with entities like projects, components, and tasks",
entityTypes: ["project", "component", "task", "issue", "commit"],
command: "node",
args: [path.resolve(__dirname, "../developer/index.js")]
},
{
name: "project",
description: "Project management context with entities like projects, tasks, and resources",
entityTypes: ["project", "task", "resource", "milestone", "risk"],
command: "node",
args: [path.resolve(__dirname, "../project/index.js")]
},
{
name: "student",
description: "Educational context with entities like courses, assignments, and exams",
entityTypes: ["course", "assignment", "exam", "note", "grade"],
command: "node",
args: [path.resolve(__dirname, "../student/index.js")]
},
{
name: "qualitativeresearch",
description: "Qualitative research context with entities like studies, participants, and interviews",
entityTypes: ["study", "participant", "interview", "code", "theme"],
command: "node",
args: [path.resolve(__dirname, "../qualitativeresearch/index.js")]
},
{
name: "quantitativeresearch",
description: "Quantitative research context with entities like datasets, variables, and analyses",
entityTypes: ["dataset", "variable", "analysis", "model", "result"],
command: "node",
args: [path.resolve(__dirname, "../quantitativeresearch/index.js")]
}
];
// Domain clients
const domainClients: { [key: string]: DomainClient } = {};
// Initialize domain clients
for (const domain of domains) {
domainClients[domain.name] = new DomainClient(domain);
}
// In-memory flow management
const flows: FlowInfo[] = [];
let activeDomain: string | null = null;
let flowCounter = 0;
// Function to get tool description based on active domain
function getToolDescription(toolName: string, domain: string | null): string {
if (!domain) {
domain = "common";
}
const descriptionFilePath = path.resolve(
__dirname,
"descriptions",
`${domain}_${toolName}.txt`
);
try {
if (existsSync(descriptionFilePath)) {
return readFileSync(descriptionFilePath, "utf8");
} else {
console.warn(`Description file not found: ${descriptionFilePath}`);
return `${toolName} tool for ${domain} domain`;
}
} catch (error) {
console.error(`Error reading description file for ${domain}_${toolName}:`, error);
return `${toolName} tool for ${domain} domain`;
}
}
// Create an MCP server
const server = new McpServer({
name: "Context Manager",
version: "1.0.0"
});
// Store tool schemas and callbacks for re-registration when domain changes
const toolDefinitions: {
[key: string]: {
schema: any;
callback: (...args: any[]) => Promise<any>;
}
} = {};
// Function to register or update a tool with domain-specific description
function registerDomainTool(
name: string,
schema: any,
callback: (...args: any[]) => Promise<any>
): void {
// Store the tool definition for later re-registration
toolDefinitions[name] = {
schema,
callback
};
// Register the tool with the current domain description
server.tool(
name,
getToolDescription(name, activeDomain),
schema,
callback
);
}
// Function to update all tool descriptions when domain changes
function updateDomainToolDescriptions(): void {
// Re-register all stored tools with new descriptions
for (const [name, definition] of Object.entries(toolDefinitions)) {
server.removeTool(name);
server.tool(
name,
getToolDescription(name, activeDomain),
definition.schema,
definition.callback
);
}
}
// Domain management tools
server.tool(
"setActiveDomain",
"Change the active domain for subsequent tool calls",
{ domain: z.string() },
async ({ domain }) => {
const foundDomain = domains.find(d => d.name.toLowerCase() === domain.toLowerCase());
if (!foundDomain) {
return {
content: [{ type: "text", text: `Error: Domain '${domain}' not found. Available domains: ${domains.map(d => d.name).join(", ")}` }],
isError: true
};
}
// Connect to the domain server
const domainClient = domainClients[foundDomain.name];
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${domain}'` }],
isError: true
};
}
activeDomain = foundDomain.name;
// Update tool descriptions for the new active domain
updateDomainToolDescriptions();
return {
content: [{ type: "text", text: `Active domain set to: ${activeDomain}` }]
};
}
);
// Flow management tools
registerDomainTool(
"startsession",
{
domain: z.string()
},
async ({ domain }) => {
const foundDomain = domains.find(d => d.name.toLowerCase() === domain.toLowerCase());
if (!foundDomain) {
return {
content: [{ type: "text", text: `Error: Domain '${domain}' not found. Available domains: ${domains.map(d => d.name).join(", ")}` }],
isError: true
};
}
// Connect to the domain server
const domainClient = domainClients[foundDomain.name];
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${domain}'` }],
isError: true
};
}
activeDomain = foundDomain.name;
flowCounter++;
// Update tool descriptions for the new active domain since activeDomain changed
updateDomainToolDescriptions();
// Forward the startsession call to the domain server with a domain-specific session identifier
try {
const result = await domainClient.callTool({
name: "startsession",
arguments: {}
});
const lastWord = result.content[0].text.split(' ').pop();
// Create a contextmanager flow ID from domain session ID (last word)
const flowId = `flow_${lastWord}`;
flows.push({
id: flowId,
domain: activeDomain,
active: true,
createdAt: Date.now()
});
return {
content: [
{ type: "text", text: `${result.content[0].text}`}
],
};
} catch (error) {
console.error(`Error starting session for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error starting session for domain ${activeDomain}: ${error}` }],
isError: true
};
}
}
);
registerDomainTool(
"endsession",
{
sessionId: z.string(),
stage: z.string(),
stageNumber: z.number(),
totalStages: z.number(),
nextStageNeeded: z.boolean(),
analysis: z.string().optional(),
isRevision: z.boolean().optional(),
revisesStage: z.number().optional(),
stageData: z.record(z.string(), z.any()).optional()
},
async ({ sessionId, stage, stageNumber, totalStages, nextStageNeeded, analysis, isRevision, revisesStage, stageData }) => {
const flowId = `flow_${sessionId}`;
const flowIndex = flows.findIndex(s => s.id === flowId);
if (flowIndex === -1) {
return {
content: [{ type: "text", text: `Error: Context Manager flow with ID '${flowId}' not found.` }],
isError: true
};
}
const flow = flows[flowIndex];
const domainName = flow.domain;
const domainClient = domainClients[domainName];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${domainName}'` }],
isError: true
};
}
}
// Forward the endsession call to the domain server
try {
const result = await domainClient.callTool({
name: "endsession",
arguments: {
sessionId: sessionId,
stage,
stageNumber,
totalStages,
nextStageNeeded,
analysis,
isRevision,
revisesStage,
stageData
}
});
if (!nextStageNeeded) {
flows[flowIndex].active = false;
return {
content: [{ type: "text", text: `${result.content[0].text}`}]
}
}
return {
content: [{ type: "text", text: `${result.content[0].text}` }]
};
} catch (error) {
console.error(`Error ending session for domain ${domainName}:`, error);
return {
content: [{ type: "text", text: `Error ending session for domain ${domainName}: ${error}` }],
isError: true
};
}
}
);
// Context management tools
registerDomainTool(
"buildcontext",
{
type: z.enum(["entities", "relations", "observations"]),
data: z.array(z.any())
},
async ({ type, data }) => {
if (!activeDomain) {
return {
content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain or startsession tool first." }],
isError: true
};
}
const domainClient = domainClients[activeDomain];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
isError: true
};
}
}
// Forward the buildcontext call to the domain server
try {
const result = await domainClient.callTool({
name: "buildcontext",
arguments: {
type,
data
}
});
return result;
} catch (error) {
console.error(`Error building context for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error building context for domain ${activeDomain}: ${error}` }],
isError: true
};
}
}
);
registerDomainTool(
"deletecontext",
{
type: z.enum(["entities", "relations", "observations"]),
data: z.array(z.any())
},
async ({ type, data }) => {
if (!activeDomain) {
return {
content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain or startsession tool first." }],
isError: true
};
}
const domainClient = domainClients[activeDomain];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
isError: true
};
}
}
// Forward the deletecontext call to the domain server
try {
const result = await domainClient.callTool({
name: "deletecontext",
arguments: {
type,
data
}
});
return result;
} catch (error) {
console.error(`Error deleting context for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error deleting context for domain ${activeDomain}: ${error}` }],
isError: true
};
}
}
);
registerDomainTool(
"loadcontext",
{
entityName: z.string(),
entityType: z.string().optional(),
sessionId: z.string().optional()
},
async ({ entityName, entityType, sessionId }) => {
if (!activeDomain) {
return {
content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain or startsession tool first." }],
isError: true
};
}
const flowId = `flow_${sessionId}`;
// Find active contextmanager flow or use provided sessionId
let targetFlow: FlowInfo | undefined;
if (sessionId) {
targetFlow = flows.find(s => s.id === flowId);
} else {
targetFlow = flows.find(s => s.domain === activeDomain && s.active);
}
if (!targetFlow) {
return {
content: [{ type: "text", text: "Error: No active Context Manager flow found. Start a flow first." }],
isError: true
};
}
const domainClient = domainClients[activeDomain];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
isError: true
};
}
}
// Update contextmanager flow with entity details
targetFlow.entityName = entityName;
targetFlow.entityType = entityType || "unknown";
// Forward the loadcontext call to the domain server
try {
const result = await domainClient.callTool({
name: "loadcontext",
arguments: {
entityName,
entityType,
sessionId: sessionId
}
});
return {
content: [{
type: "text",
text: `${result.content[0].text}`
}]
};
} catch (error) {
console.error(`Error loading context for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error loading context for domain ${activeDomain}: ${error}` }],
isError: true
};
}
}
);
registerDomainTool(
"advancedcontext",
{
type: z.string(),
params: z.record(z.string(), z.any())
},
async ({ type, params }) => {
if (!activeDomain) {
return {
content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain or startsession tool first." }],
isError: true
};
}
const domainClient = domainClients[activeDomain];
if (!domainClient || !domainClient.connected) {
const connected = await domainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
isError: true
};
}
}
// Forward the advancedcontext call to the domain server
try {
const result = await domainClient.callTool({
name: "advancedcontext",
arguments: {
type,
params
}
});
return result;
} catch (error) {
console.error(`Error performing advanced context operation for domain ${activeDomain}:`, error);
return {
content: [{ type: "text", text: `Error performing advanced context operation for domain ${activeDomain}: ${error}` }],
isError: true
};
}
}
);
// // List all entities
// server.tool(
// "listAllEntities",
// {
// domain: z.string().optional()
// },
// async () => {
// if (!activeDomain) {
// return {
// content: [{ type: "text", text: "Error: No active domain set. Use setActiveDomain or startsession tool first." }],
// isError: true
// };
// }
// const domainClient = domainClients[activeDomain];
// if (!domainClient || !domainClient.connected) {
// const connected = await domainClient.connect();
// if (!connected) {
// return {
// content: [{ type: "text", text: `Error: Could not connect to domain server for '${activeDomain}'` }],
// isError: true
// };
// }
// }
// // Forward the listAllEntities call to the domain server
// try {
// const result = await domainClient.callTool({
// name: "listAllEntities",
// arguments: {}
// });
// return result;
// } catch (error) {
// // If the domain server doesn't support listAllEntities, fall back to our domain registry
// const domain = domains.find(d => d.name === activeDomain);
// if (!domain) {
// return {
// content: [{ type: "text", text: `Error: Domain '${activeDomain}' not found.` }],
// isError: true
// };
// }
// return {
// content: [{
// type: "text",
// text: `Available entity types in ${activeDomain} domain: ${domain.entityTypes.join(", ")}`
// }]
// };
// }
// }
// );
// Domain discovery resources
server.resource(
"domains",
"domains://list",
async (uri) => ({
contents: [{
uri: uri.href,
text: JSON.stringify(domains, null, 2)
}]
})
);
// Cross-domain functionality
server.tool(
"relateCrossDomain",
"Create connections between entities across different domains",
{
fromDomain: z.string(),
fromEntity: z.string(),
toDomain: z.string(),
toEntity: z.string(),
relationType: z.string()
},
async ({ fromDomain, fromEntity, toDomain, toEntity, relationType }) => {
// Validate domains
const fromDomainInfo = domains.find(d => d.name.toLowerCase() === fromDomain.toLowerCase());
const toDomainInfo = domains.find(d => d.name.toLowerCase() === toDomain.toLowerCase());
if (!fromDomainInfo) {
return {
content: [{ type: "text", text: `Error: Source domain '${fromDomain}' not found.` }],
isError: true
};
}
if (!toDomainInfo) {
return {
content: [{ type: "text", text: `Error: Target domain '${toDomain}' not found.` }],
isError: true
};
}
// Create cross-domain relation by adding observations to both entities
try {
// Connect to source domain
const fromDomainClient = domainClients[fromDomainInfo.name];
if (!fromDomainClient || !fromDomainClient.connected) {
const connected = await fromDomainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${fromDomain}'` }],
isError: true
};
}
}
// Connect to target domain
const toDomainClient = domainClients[toDomainInfo.name];
if (!toDomainClient || !toDomainClient.connected) {
const connected = await toDomainClient.connect();
if (!connected) {
return {
content: [{ type: "text", text: `Error: Could not connect to domain server for '${toDomain}'` }],
isError: true
};
}
}
// Add observation to source entity about relation to target entity
await fromDomainClient.callTool({
name: "buildcontext",
arguments: {
type: "observations",
data: {
observations: [
{
entityName: fromEntity,
contents: [`Related to ${toEntity} (${toDomainInfo.name} domain) via ${relationType}`]
}
]
}
}
});
// Add observation to target entity about relation from source entity
await toDomainClient.callTool({
name: "buildcontext",
arguments: {
type: "observations",
data: {
observations: [
{
entityName: toEntity,
contents: [`Related from ${fromEntity} (${fromDomainInfo.name} domain) via ${relationType}`]
}
]
}
}
});
return {
content: [{
type: "text",
text: `Created cross-domain relation: ${fromEntity} (${fromDomain}) --[${relationType}]--> ${toEntity} (${toDomain})`
}]
};
} catch (error) {
console.error(`Error creating cross-domain relation:`, error);
return {
content: [{ type: "text", text: `Error creating cross-domain relation: ${error}` }],
isError: true
};
}
}
);
// Main function to start the server
async function main() {
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
}
// Execute the main function
main().catch(error => {
console.error("Error in contextmanager:", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/developer/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { promises as fs } from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { z } from "zod";
import { readFileSync, existsSync } from "fs";
// Define memory file path using environment variable with fallback
const parentPath = path.dirname(fileURLToPath(import.meta.url));
const defaultMemoryPath = path.join(parentPath, 'memory.json');
const defaultSessionsPath = path.join(parentPath, 'sessions.json');
// Properly handle absolute and relative paths for MEMORY_FILE_PATH
const MEMORY_FILE_PATH = process.env.MEMORY_FILE_PATH
? path.isAbsolute(process.env.MEMORY_FILE_PATH)
? process.env.MEMORY_FILE_PATH // Use absolute path as is
: path.join(process.cwd(), process.env.MEMORY_FILE_PATH) // Relative to current working directory
: defaultMemoryPath; // Default fallback
// Properly handle absolute and relative paths for SESSIONS_FILE_PATH
const SESSIONS_FILE_PATH = process.env.SESSIONS_FILE_PATH
? path.isAbsolute(process.env.SESSIONS_FILE_PATH)
? process.env.SESSIONS_FILE_PATH // Use absolute path as is
: path.join(process.cwd(), process.env.SESSIONS_FILE_PATH) // Relative to current working directory
: defaultSessionsPath; // Default fallback
// Software Development specific entity types
const VALID_ENTITY_TYPES = [
'project', // Overall software project
'component', // Module, service, or package within a project
'feature', // Specific functionality being developed
'issue', // Bug or problem to be fixed
'task', // Work item or activity needed for development
'technology', // Language, framework, or tool used
'decision', // Important technical or architectural decision
'milestone', // Key project deadline or phase
'environment', // Development, staging, production environments
'documentation', // Project documentation
'requirement', // Project requirement or specification
'status', // Entity status (inactive, active, or complete)
'priority' // Entity priority (low or high)
];
// Software Development specific relation types
const VALID_RELATION_TYPES = [
'depends_on', // Dependency relationship
'implements', // Component implements a feature
'blocked_by', // Task is blocked by an issue
'uses', // Component uses a technology
'part_of', // Component is part of a project
'contains', // Project contains a component
'related_to', // General relationship
'affects', // Issue affects a component
'resolves', // Task resolves an issue
'documented_in', // Component is documented in documentation
'decided_in', // Decision was made in a meeting
'required_by', // Feature is required by a requirement
'has_status', // Entity has a particular status
'has_priority', // Entity has a particular priority
'depends_on_milestone', // Task depends on reaching a milestone
'precedes', // Task precedes another task (for sequencing)
'tested_in' // Component is tested in an environment
];
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Collect tool descriptions from text files
const toolDescriptions: Record<string, string> = {
'startsession': '',
'loadcontext': '',
'deletecontext': '',
'buildcontext': '',
'advancedcontext': '',
'endsession': '',
};
for (const tool of Object.keys(toolDescriptions)) {
try {
const descriptionFilePath = path.resolve(
__dirname,
`developer_${tool}.txt`
);
if (existsSync(descriptionFilePath)) {
toolDescriptions[tool] = readFileSync(descriptionFilePath, 'utf-8');
}
} catch (error) {
console.error(`Error reading description file for tool '${tool}': ${error}`);
}
}
// Session management functions
async function loadSessionStates(): Promise<Map<string, any[]>> {
try {
const fileContent = await fs.readFile(SESSIONS_FILE_PATH, 'utf-8');
const sessions = JSON.parse(fileContent);
// Convert from object to Map
const sessionsMap = new Map<string, any[]>();
for (const [key, value] of Object.entries(sessions)) {
sessionsMap.set(key, value as any[]);
}
return sessionsMap;
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
return new Map<string, any[]>();
}
throw error;
}
}
async function saveSessionStates(sessionsMap: Map<string, any[]>): Promise<void> {
// Convert from Map to object
const sessions: Record<string, any[]> = {};
for (const [key, value] of sessionsMap.entries()) {
sessions[key] = value;
}
await fs.writeFile(SESSIONS_FILE_PATH, JSON.stringify(sessions, null, 2), 'utf-8');
}
// Generate a unique session ID
function generateSessionId(): string {
return `dev_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
}
// Basic validation functions
function validateEntityType(entityType: string): boolean {
return VALID_ENTITY_TYPES.includes(entityType);
}
function validateRelationType(relationType: string): boolean {
return VALID_RELATION_TYPES.includes(relationType);
}
// We are storing our memory using entities, relations, and observations in a graph structure
interface Entity {
name: string;
entityType: string;
observations: string[];
}
interface Relation {
from: string;
to: string;
relationType: string;
}
interface KnowledgeGraph {
entities: Entity[];
relations: Relation[];
}
// Define the valid status and priority values
const VALID_STATUS_VALUES = ['inactive', 'active', 'complete'];
const VALID_PRIORITY_VALUES = ['low', 'high'];
// The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
class KnowledgeGraphManager {
private async loadGraph(): Promise<KnowledgeGraph> {
try {
const fileContent = await fs.readFile(MEMORY_FILE_PATH, 'utf-8');
return JSON.parse(fileContent);
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
return { entities: [], relations: [] };
}
throw error;
}
}
private async saveGraph(graph: KnowledgeGraph): Promise<void> {
await fs.writeFile(MEMORY_FILE_PATH, JSON.stringify(graph, null, 2), 'utf-8');
}
// Initialize status and priority entities
async initializeStatusAndPriority(): Promise<void> {
const graph = await this.loadGraph();
// Create status entities if they don't exist
for (const statusValue of VALID_STATUS_VALUES) {
const statusName = `status:${statusValue}`;
if (!graph.entities.some(e => e.name === statusName && e.entityType === 'status')) {
graph.entities.push({
name: statusName,
entityType: 'status',
observations: [`A ${statusValue} status value`]
});
}
}
// Create priority entities if they don't exist
for (const priorityValue of VALID_PRIORITY_VALUES) {
const priorityName = `priority:${priorityValue}`;
if (!graph.entities.some(e => e.name === priorityName && e.entityType === 'priority')) {
graph.entities.push({
name: priorityName,
entityType: 'priority',
observations: [`A ${priorityValue} priority value`]
});
}
}
await this.saveGraph(graph);
}
// Helper method to get status of an entity
async getEntityStatus(entityName: string): Promise<string | null> {
const graph = await this.loadGraph();
// Find status relation for this entity
const statusRelation = graph.relations.find(r =>
r.from === entityName &&
r.relationType === 'has_status'
);
if (statusRelation) {
// Extract status value from the status entity name (status:value)
return statusRelation.to.split(':')[1];
}
return null;
}
// Helper method to get priority of an entity
async getEntityPriority(entityName: string): Promise<string | null> {
const graph = await this.loadGraph();
// Find priority relation for this entity
const priorityRelation = graph.relations.find(r =>
r.from === entityName &&
r.relationType === 'has_priority'
);
if (priorityRelation) {
// Extract priority value from the priority entity name (priority:value)
return priorityRelation.to.split(':')[1];
}
return null;
}
// Helper method to set status of an entity
async setEntityStatus(entityName: string, statusValue: string): Promise<void> {
if (!VALID_STATUS_VALUES.includes(statusValue)) {
throw new Error(`Invalid status value: ${statusValue}. Valid values are: ${VALID_STATUS_VALUES.join(', ')}`);
}
const graph = await this.loadGraph();
// Remove any existing status relations for this entity
graph.relations = graph.relations.filter(r =>
!(r.from === entityName && r.relationType === 'has_status')
);
// Add new status relation
graph.relations.push({
from: entityName,
to: `status:${statusValue}`,
relationType: 'has_status'
});
await this.saveGraph(graph);
}
// Helper method to set priority of an entity
async setEntityPriority(entityName: string, priorityValue: string): Promise<void> {
if (!VALID_PRIORITY_VALUES.includes(priorityValue)) {
throw new Error(`Invalid priority value: ${priorityValue}. Valid values are: ${VALID_PRIORITY_VALUES.join(', ')}`);
}
const graph = await this.loadGraph();
// Remove any existing priority relations for this entity
graph.relations = graph.relations.filter(r =>
!(r.from === entityName && r.relationType === 'has_priority')
);
// Add new priority relation
graph.relations.push({
from: entityName,
to: `priority:${priorityValue}`,
relationType: 'has_priority'
});
await this.saveGraph(graph);
}
async createEntities(entities: Entity[]): Promise<Entity[]> {
// Validate entity types
for (const entity of entities) {
if (!validateEntityType(entity.entityType)) {
throw new Error(`Invalid entity type: ${entity.entityType}. Valid types are: ${VALID_ENTITY_TYPES.join(', ')}`);
}
}
const graph = await this.loadGraph();
const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name));
graph.entities.push(...newEntities);
await this.saveGraph(graph);
return newEntities;
}
async createRelations(relations: Relation[]): Promise<Relation[]> {
// Validate relation types
for (const relation of relations) {
if (!validateRelationType(relation.relationType)) {
throw new Error(`Invalid relation type: ${relation.relationType}. Valid types are: ${VALID_RELATION_TYPES.join(', ')}`);
}
}
const graph = await this.loadGraph();
// Check if entities exist
for (const relation of relations) {
const fromEntity = graph.entities.find(e => e.name === relation.from);
const toEntity = graph.entities.find(e => e.name === relation.to);
if (!fromEntity) {
throw new Error(`Source entity '${relation.from}' does not exist. Please create it first.`);
}
if (!toEntity) {
throw new Error(`Target entity '${relation.to}' does not exist. Please create it first.`);
}
}
const newRelations = relations.filter(r => !graph.relations.some(existingRelation =>
existingRelation.from === r.from &&
existingRelation.to === r.to &&
existingRelation.relationType === r.relationType
));
graph.relations.push(...newRelations);
await this.saveGraph(graph);
return newRelations;
}
async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]> {
const graph = await this.loadGraph();
const results = observations.map(o => {
const entity = graph.entities.find(e => e.name === o.entityName);
if (!entity) {
throw new Error(`Entity with name ${o.entityName} not found`);
}
const newObservations = o.contents.filter(content => !entity.observations.includes(content));
entity.observations.push(...newObservations);
return { entityName: o.entityName, addedObservations: newObservations };
});
await this.saveGraph(graph);
return results;
}
async deleteEntities(entityNames: string[]): Promise<void> {
const graph = await this.loadGraph();
graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to));
await this.saveGraph(graph);
}
async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> {
const graph = await this.loadGraph();
deletions.forEach(d => {
const entity = graph.entities.find(e => e.name === d.entityName);
if (entity) {
entity.observations = entity.observations.filter(o => !d.observations.includes(o));
}
});
await this.saveGraph(graph);
}
async deleteRelations(relations: Relation[]): Promise<void> {
const graph = await this.loadGraph();
graph.relations = graph.relations.filter(r => !relations.some(delRelation =>
r.from === delRelation.from &&
r.to === delRelation.to &&
r.relationType === delRelation.relationType
));
await this.saveGraph(graph);
}
async readGraph(): Promise<KnowledgeGraph> {
return this.loadGraph();
}
// Basic search function
async searchNodes(query: string): Promise<KnowledgeGraph> {
const graph = await this.loadGraph();
// Filter entities
const filteredEntities = graph.entities.filter(e =>
e.name.toLowerCase().includes(query.toLowerCase()) ||
e.entityType.toLowerCase().includes(query.toLowerCase()) ||
e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()))
);
// Create a Set of filtered entity names for quick lookup
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
// Filter relations to only include those between filtered entities
const filteredRelations = graph.relations.filter(r =>
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
);
const filteredGraph: KnowledgeGraph = {
entities: filteredEntities,
relations: filteredRelations,
};
return filteredGraph;
}
async openNodes(names: string[]): Promise<KnowledgeGraph> {
const graph = await this.loadGraph();
// Filter entities
const filteredEntities = graph.entities.filter(e => names.includes(e.name));
// Create a Set of filtered entity names for quick lookup
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
// Filter relations to only include those between filtered entities
const filteredRelations = graph.relations.filter(r =>
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
);
const filteredGraph: KnowledgeGraph = {
entities: filteredEntities,
relations: filteredRelations,
};
return filteredGraph;
}
// Software Development specific functions
// Get project overview including components, features, issues, etc.
async getProjectStatus(projectName: string): Promise<any> {
const graph = await this.loadGraph();
// Find the project entity
const project = graph.entities.find(e => e.name === projectName && e.entityType === 'project');
if (!project) {
throw new Error(`Project '${projectName}' not found`);
}
// Find components that are part of this project
const components: Entity[] = [];
// Find features, issues, tasks, milestones related to this project
const features: Entity[] = [];
const issues: Entity[] = [];
const tasks: Entity[] = [];
const milestones: Entity[] = [];
// Find entities directly related to the project
for (const relation of graph.relations) {
if (relation.from === projectName || relation.to === projectName) {
const relatedEntity = graph.entities.find(e =>
(relation.from === projectName && e.name === relation.to) ||
(relation.to === projectName && e.name === relation.from)
);
if (relatedEntity) {
if (relatedEntity.entityType === 'component') components.push(relatedEntity);
if (relatedEntity.entityType === 'feature') features.push(relatedEntity);
if (relatedEntity.entityType === 'issue') issues.push(relatedEntity);
if (relatedEntity.entityType === 'task') tasks.push(relatedEntity);
if (relatedEntity.entityType === 'milestone') milestones.push(relatedEntity);
}
}
}
// Find entities related to components of the project
for (const component of components) {
for (const relation of graph.relations) {
if (relation.from === component.name || relation.to === component.name) {
const relatedEntity = graph.entities.find(e =>
(relation.from === component.name && e.name === relation.to) ||
(relation.to === component.name && e.name === relation.from)
);
if (relatedEntity) {
if (relatedEntity.entityType === 'feature' && !features.some(f => f.name === relatedEntity.name)) {
features.push(relatedEntity);
}
if (relatedEntity.entityType === 'issue' && !issues.some(i => i.name === relatedEntity.name)) {
issues.push(relatedEntity);
}
if (relatedEntity.entityType === 'task' && !tasks.some(t => t.name === relatedEntity.name)) {
tasks.push(relatedEntity);
}
}
}
}
}
// Get active tasks and issues
const statuses: Record<string, string> = {};
const priorities: Record<string, string> = {};
// Load status and priority for tasks and issues
for (const entity of [...tasks, ...issues, ...features, ...milestones]) {
const status = await this.getEntityStatus(entity.name);
if (status) {
statuses[entity.name] = status;
}
const priority = await this.getEntityPriority(entity.name);
if (priority) {
priorities[entity.name] = priority;
}
}
// Filter active tasks and issues based on status
const activeTasks = tasks.filter(task => {
const status = statuses[task.name];
return status ? status === 'active' : true;
});
const activeIssues = issues.filter(issue => {
const status = statuses[issue.name];
return status ? status === 'active' : true;
});
// Find upcoming milestones
const upcomingMilestones = milestones.filter(milestone => {
const status = statuses[milestone.name];
return status ? status === 'active' : true;
});
// Get decision history
const decisions = graph.entities.filter(e =>
e.entityType === 'decision' &&
graph.relations.some(r =>
(r.from === e.name && r.to === projectName) ||
(r.to === e.name && r.from === projectName)
)
);
// Find task sequencing
const taskSequencing: Record<string, string[]> = {};
for (const task of tasks) {
const precedingTasks: string[] = [];
const followingTasks: string[] = [];
// Find tasks that this task precedes
for (const relation of graph.relations) {
if (relation.from === task.name && relation.relationType === 'precedes') {
followingTasks.push(relation.to);
}
if (relation.to === task.name && relation.relationType === 'precedes') {
precedingTasks.push(relation.from);
}
}
if (precedingTasks.length > 0 || followingTasks.length > 0) {
taskSequencing[task.name] = {
precedingTasks,
followingTasks
} as any;
}
}
return {
project,
components,
activeFeatures: features.filter(f => {
const status = statuses[f.name];
return status ? status === 'active' : true;
}),
activeTasks,
activeIssues,
upcomingMilestones,
allFeatures: features,
allIssues: issues,
allTasks: tasks,
allMilestones: milestones,
recentDecisions: decisions.slice(0, 5), // Limit to 5 most recent decisions
statuses, // Include status mapping for reference
priorities, // Include priority mapping for reference
taskSequencing // Include task sequencing information
};
}
// Get detailed context for a specific component
async getComponentContext(componentName: string): Promise<any> {
const graph = await this.loadGraph();
// Find the component entity
const component = graph.entities.find(e => e.name === componentName && e.entityType === 'component');
if (!component) {
throw new Error(`Component '${componentName}' not found`);
}
// Find projects this component is part of
const projects: Entity[] = [];
for (const relation of graph.relations) {
if (relation.relationType === 'contains' && relation.to === componentName) {
const project = graph.entities.find(e => e.name === relation.from && e.entityType === 'project');
if (project) {
projects.push(project);
}
}
}
// Find features implemented by this component
const features: Entity[] = [];
for (const relation of graph.relations) {
if (relation.relationType === 'implements' && relation.from === componentName) {
const feature = graph.entities.find(e => e.name === relation.to && e.entityType === 'feature');
if (feature) {
features.push(feature);
}
}
}
// Find technologies used by this component
const technologies: Entity[] = [];
for (const relation of graph.relations) {
if (relation.relationType === 'uses' && relation.from === componentName) {
const technology = graph.entities.find(e => e.name === relation.to && e.entityType === 'technology');
if (technology) {
technologies.push(technology);
}
}
}
// Find issues affecting this component
const issues: Entity[] = [];
for (const relation of graph.relations) {
if (relation.relationType === 'affects' && relation.to === componentName) {
const issue = graph.entities.find(e => e.name === relation.from && e.entityType === 'issue');
if (issue) {
issues.push(issue);
}
}
}
// Find tasks related to this component
const tasks = [];
for (const relation of graph.relations) {
if ((relation.from === componentName || relation.to === componentName) &&
graph.entities.some(e =>
(e.name === relation.from || e.name === relation.to) &&
e.name !== componentName &&
e.entityType === 'task'
)) {
const task = graph.entities.find(e =>
(e.name === relation.from || e.name === relation.to) &&
e.name !== componentName &&
e.entityType === 'task'
);
if (task) {
tasks.push(task);
}
}
}
// Find documentation for this component
const documentation = [];
for (const relation of graph.relations) {
if (relation.relationType === 'documented_in' && relation.from === componentName) {
const doc = graph.entities.find(e => e.name === relation.to && e.entityType === 'documentation');
if (doc) {
documentation.push(doc);
}
}
}
// Find dependencies
const dependencies = [];
for (const relation of graph.relations) {
if (relation.relationType === 'depends_on' && relation.from === componentName) {
const dependency = graph.entities.find(e => e.name === relation.to);
if (dependency) {
dependencies.push(dependency);
}
}
}
// Get statuses and priorities for tasks and issues
const statuses: Record<string, string> = {};
const priorities: Record<string, string> = {};
// Load status and priority for tasks and issues
for (const entity of [...tasks, ...issues, ...features]) {
const status = await this.getEntityStatus(entity.name);
if (status) {
statuses[entity.name] = status;
}
const priority = await this.getEntityPriority(entity.name);
if (priority) {
priorities[entity.name] = priority;
}
}
return {
component,
projects,
features,
technologies,
activeIssues: issues.filter(issue => {
const status = statuses[issue.name];
return status ? status === 'active' : true;
}),
activeTasks: tasks.filter(task => {
const status = statuses[task.name];
return status ? status === 'active' : true;
}),
documentation,
dependencies,
allIssues: issues,
allTasks: tasks,
statuses,
priorities
};
}
// Get all entities related to a specific entity
async getRelatedEntities(entityName: string, relationTypes?: string[]): Promise<any> {
const graph = await this.loadGraph();
// Find the entity
const entity = graph.entities.find(e => e.name === entityName);
if (!entity) {
throw new Error(`Entity '${entityName}' not found`);
}
// Find all relations involving this entity
let relevantRelations = graph.relations.filter(r => r.from === entityName || r.to === entityName);
// Filter by relation types if specified
if (relationTypes && relationTypes.length > 0) {
relevantRelations = relevantRelations.filter(r => relationTypes.includes(r.relationType));
}
// Get all related entities
const related = {
entity,
incomingRelations: [] as { relation: Relation; source: Entity }[],
outgoingRelations: [] as { relation: Relation; target: Entity }[],
};
for (const relation of relevantRelations) {
if (relation.from === entityName) {
const target = graph.entities.find(e => e.name === relation.to);
if (target) {
related.outgoingRelations.push({
relation,
target
});
}
} else {
const source = graph.entities.find(e => e.name === relation.from);
if (source) {
related.incomingRelations.push({
relation,
source
});
}
}
}
return related;
}
// Get the history of decisions related to a project
async getDecisionHistory(projectName: string): Promise<any> {
const graph = await this.loadGraph();
// Find the project
const project = graph.entities.find(e => e.name === projectName && e.entityType === "project");
if (!project) {
throw new Error(`Project '${projectName}' not found`);
}
// Find all decision entities related to this project
const decisions: Entity[] = [];
// Direct decision relations to the project
for (const relation of graph.relations) {
if (relation.relationType === "related_to" && relation.to === projectName) {
const decision = graph.entities.find(e => e.name === relation.from && e.entityType === "decision");
if (decision) {
decisions.push(decision);
}
}
}
// Decisions related to components of the project
const components: Entity[] = [];
for (const relation of graph.relations) {
if (relation.relationType === "contains" && relation.from === projectName) {
const component = graph.entities.find(e => e.name === relation.to && e.entityType === "component");
if (component) {
components.push(component);
}
}
}
for (const component of components) {
for (const relation of graph.relations) {
if (relation.relationType === "related_to" && relation.to === component.name) {
const decision = graph.entities.find(e => e.name === relation.from && e.entityType === "decision");
if (decision && !decisions.some(d => d.name === decision.name)) {
decisions.push(decision);
}
}
}
}
// Sort decisions chronologically if they have date observations
const decisionsWithDates = decisions.map(decision => {
const dateObs = decision.observations.find(o => o.startsWith('Date:'));
return {
decision,
date: dateObs ? new Date(dateObs.split(':')[1].trim()) : new Date(0)
};
});
decisionsWithDates.sort((a, b) => b.date.getTime() - a.date.getTime());
return {
project,
decisions: decisionsWithDates.map(d => d.decision),
};
}
// Get progress toward a milestone
async getMilestoneProgress(milestoneName: string): Promise<any> {
const graph = await this.loadGraph();
// Find the milestone
const milestone = graph.entities.find(e => e.name === milestoneName && e.entityType === "milestone");
if (!milestone) {
throw new Error(`Milestone '${milestoneName}' not found`);
}
// Find all tasks related to this milestone
const tasks: Entity[] = [];
for (const relation of graph.relations) {
if (relation.relationType === "related_to" && relation.to === milestoneName) {
const task = graph.entities.find(e => e.name === relation.from && e.entityType === "task");
if (task) {
tasks.push(task);
}
}
}
// Get statuses for all tasks
const statuses: Record<string, string> = {};
// Load status for tasks
for (const task of tasks) {
const status = await this.getEntityStatus(task.name);
if (status) {
statuses[task.name] = status;
}
}
// Group tasks by status
const completedTasks: Entity[] = [];
const inProgressTasks: Entity[] = [];
const notStartedTasks: Entity[] = [];
for (const task of tasks) {
const status = statuses[task.name] || 'inactive';
if (status === 'complete') {
completedTasks.push(task);
} else if (status === 'active') {
inProgressTasks.push(task);
} else {
notStartedTasks.push(task);
}
}
// Calculate progress percentage
const totalTasks = tasks.length;
const progressPercentage = totalTasks > 0
? Math.round((completedTasks.length / totalTasks) * 100)
: 0;
// Find task sequencing
const taskSequencing: Record<string, any> = {};
for (const task of tasks) {
const precedingTasks: string[] = [];
const followingTasks: string[] = [];
// Find tasks that this task precedes
for (const relation of graph.relations) {
if (relation.from === task.name && relation.relationType === 'precedes') {
followingTasks.push(relation.to);
}
if (relation.to === task.name && relation.relationType === 'precedes') {
precedingTasks.push(relation.from);
}
}
if (precedingTasks.length > 0 || followingTasks.length > 0) {
taskSequencing[task.name] = {
precedingTasks,
followingTasks
};
}
}
// Determine if milestone can be considered complete
const milestoneComplete = tasks.length > 0 && tasks.every(task =>
statuses[task.name] === 'complete'
);
return {
milestone,
progress: {
totalTasks,
completedTasks: completedTasks.length,
inProgressTasks: inProgressTasks.length,
notStartedTasks: notStartedTasks.length,
percentage: progressPercentage,
complete: milestoneComplete
},
tasks: {
completed: completedTasks,
inProgress: inProgressTasks,
notStarted: notStartedTasks
},
taskSequencing,
statuses
};
}
}
// Main function to set up the MCP server
async function main() {
try {
const knowledgeGraphManager = new KnowledgeGraphManager();
// Initialize status and priority entities
await knowledgeGraphManager.initializeStatusAndPriority();
// Initialize session states from persistent storage
const sessionStates = await loadSessionStates();
// Create the MCP server with a name and version
const server = new McpServer({
name: "Context Manager",
version: "1.0.0"
});
// Define a resource that exposes the entire graph
server.resource(
"graph",
"graph://developer",
async (uri) => ({
contents: [{
uri: uri.href,
text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2)
}]
})
);
// Define tools using zod for parameter validation
// CRUD operations - these are now consolidated into buildcontext, deletecontext, and advancedcontext tools
/**
* Create new entities, relations, and observations.
*/
server.tool(
"buildcontext",
toolDescriptions["buildcontext"],
{
type: z.enum(["entities", "relations", "observations"]).describe("Type of creation operation: 'entities', 'relations', or 'observations'"),
data: z.array(z.any()).describe("Data for the creation operation, structure varies by type but must be an array")
},
async ({ type, data }) => {
try {
let result;
switch (type) {
case "entities":
// Ensure entities match the Entity interface
const typedEntities: Entity[] = data.map((e: any) => ({
name: e.name,
entityType: e.entityType,
observations: e.observations
}));
result = await knowledgeGraphManager.createEntities(typedEntities);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, created: result }, null, 2)
}]
};
case "relations":
// Ensure relations match the Relation interface
const typedRelations: Relation[] = data.map((r: any) => ({
from: r.from,
to: r.to,
relationType: r.relationType
}));
result = await knowledgeGraphManager.createRelations(typedRelations);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, created: result }, null, 2)
}]
};
case "observations":
// Ensure observations match the required interface
const typedObservations: { entityName: string; contents: string[] }[] = data.map((o: any) => ({
entityName: o.entityName,
contents: o.contents
}));
result = await knowledgeGraphManager.addObservations(typedObservations);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, added: result }, null, 2)
}]
};
default:
throw new Error(`Invalid type: ${type}. Must be 'entities', 'relations', or 'observations'.`);
}
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : String(error)
}, null, 2)
}]
};
}
}
);
/**
* Delete entities, relations, and observations.
*/
server.tool(
"deletecontext",
toolDescriptions["deletecontext"],
{
type: z.enum(["entities", "relations", "observations"]).describe("Type of deletion operation: 'entities', 'relations', or 'observations'"),
data: z.array(z.any()).describe("Data for the deletion operation, structure varies by type but must be an array")
},
async ({ type, data }) => {
try {
switch (type) {
case "entities":
await knowledgeGraphManager.deleteEntities(data);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, message: `Deleted ${data.length} entities` }, null, 2)
}]
};
case "relations":
// Ensure relations match the Relation interface
const typedRelations: Relation[] = data.map((r: any) => ({
from: r.from,
to: r.to,
relationType: r.relationType
}));
await knowledgeGraphManager.deleteRelations(typedRelations);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, message: `Deleted ${data.length} relations` }, null, 2)
}]
};
case "observations":
// Ensure deletions match the required interface
const typedDeletions: { entityName: string; observations: string[] }[] = data.map((d: any) => ({
entityName: d.entityName,
observations: d.observations
}));
await knowledgeGraphManager.deleteObservations(typedDeletions);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, message: `Deleted observations from ${data.length} entities` }, null, 2)
}]
};
default:
throw new Error(`Invalid type: ${type}. Must be 'entities', 'relations', or 'observations'.`);
}
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : String(error)
}, null, 2)
}]
};
}
}
);
/**
* Get information about the graph, search for nodes, open nodes, get related entities, get decision history, and get milestone progress.
*/
server.tool(
"advancedcontext",
toolDescriptions["advancedcontext"],
{
type: z.enum(["graph", "search", "nodes", "related", "decisions", "milestone"]).describe("Type of get operation: 'graph', 'search', 'nodes', 'related', 'decisions', or 'milestone'"),
params: z.record(z.string(), z.any()).describe("Parameters for the operation, structure varies by type")
},
async ({ type, params }) => {
try {
let result;
switch (type) {
case "graph":
result = await knowledgeGraphManager.readGraph();
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, graph: result }, null, 2)
}]
};
case "search":
result = await knowledgeGraphManager.searchNodes(params.query);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, results: result }, null, 2)
}]
};
case "nodes":
result = await knowledgeGraphManager.openNodes(params.names);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, nodes: result }, null, 2)
}]
};
case "related":
result = await knowledgeGraphManager.getRelatedEntities(params.entityName, params.relationTypes);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, entities: result }, null, 2)
}]
};
case "decisions":
result = await knowledgeGraphManager.getDecisionHistory(params.projectName);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, decisions: result }, null, 2)
}]
};
case "milestone":
result = await knowledgeGraphManager.getMilestoneProgress(params.milestoneName);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, progress: result }, null, 2)
}]
};
default:
throw new Error(`Invalid type: ${type}. Must be 'graph', 'search', 'nodes', 'related', 'decisions', or 'milestone'.`);
}
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : String(error)
}, null, 2)
}]
};
}
}
);
/**
* Start a new development session. Returns session ID, recent development sessions, active projects, high-priority tasks, and upcoming milestones.
* The output allows the user to easily choose what to focus on and which specific context to load.
*/
server.tool(
"startsession",
toolDescriptions["startsession"],
{},
async () => {
try {
// Generate a unique session ID
const sessionId = generateSessionId();
// Get recent sessions from persistent storage
const sessionStates = await loadSessionStates();
// Initialize the session state
sessionStates.set(sessionId, []);
await saveSessionStates(sessionStates);
// Convert sessions map to array, sort by date, and take most recent ones
const recentSessions = Array.from(sessionStates.entries())
.map(([id, stages]) => {
// Extract summary data from the first stage (if it exists)
const summaryStage = stages.find(s => s.stage === "summary");
return {
id,
project: summaryStage?.stageData?.project || "Unknown project",
focus: summaryStage?.stageData?.focus || "Unknown focus",
summary: summaryStage?.stageData?.summary || "No summary available"
};
})
.slice(0, 3); // Default to showing 3 recent sessions
// Get active development projects
const graph = await knowledgeGraphManager.readGraph();
const activeProjects = [];
// Find projects with active status
for (const entity of graph.entities) {
if (entity.entityType === 'project') {
const status = await knowledgeGraphManager.getEntityStatus(entity.name);
if (status === 'active') {
activeProjects.push(entity);
}
}
}
// Get high-priority development tasks
const highPriorityTasks = [];
// Find tasks with high priority and active status
for (const entity of graph.entities) {
if (entity.entityType === 'task') {
const status = await knowledgeGraphManager.getEntityStatus(entity.name);
const priority = await knowledgeGraphManager.getEntityPriority(entity.name);
if (status === 'active' && priority === 'high') {
highPriorityTasks.push(entity);
}
}
}
// Get upcoming milestones
const upcomingMilestones = [];
// Find milestones with active status
for (const entity of graph.entities) {
if (entity.entityType === 'milestone') {
const status = await knowledgeGraphManager.getEntityStatus(entity.name);
if (status === 'active') {
upcomingMilestones.push(entity);
}
}
}
let sessionsText = "No recent sessions found.";
if (recentSessions.length > 0) {
sessionsText = recentSessions.map(session =>
`- ${session.project} - ${session.focus} - ${session.summary.substring(0, 100)}${session.summary.length > 100 ? '...' : ''}`
).join('\n');
}
let projectsText = "No active projects found.";
if (activeProjects.length > 0) {
projectsText = activeProjects.map(project => {
const obsPreview = project.observations.length > 0 ?
`: ${project.observations[0].substring(0, 60)}${project.observations[0].length > 60 ? '...' : ''}` : '';
return `- ${project.name}${obsPreview}`;
}).join('\n');
}
let tasksText = "No high-priority tasks found.";
if (highPriorityTasks.length > 0) {
tasksText = highPriorityTasks.map(task => {
const obsPreview = task.observations.length > 0 ?
`: ${task.observations[0].substring(0, 60)}${task.observations[0].length > 60 ? '...' : ''}` : '';
return `- ${task.name}${obsPreview}`;
}).join('\n');
}
let milestonesText = "No upcoming milestones found.";
if (upcomingMilestones.length > 0) {
milestonesText = upcomingMilestones.map(milestone => {
const obsPreview = milestone.observations.length > 0 ?
`: ${milestone.observations[0].substring(0, 60)}${milestone.observations[0].length > 60 ? '...' : ''}` : '';
return `- ${milestone.name}${obsPreview}`;
}).join('\n');
}
return {
content: [{
type: "text",
text: `# Ask user to choose what to focus on in this session. Present the following options:
## Recent Development Sessions
${sessionsText}
## Active Projects
${projectsText}
## High-Priority Tasks
${tasksText}
## Upcoming Milestones
${milestonesText}
To load specific context based on the user's choice, use the \`loadcontext\` tool with the entity name and developer session ID - ${sessionId}.`
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : String(error)
}, null, 2)
}]
};
}
}
);
/**
* Load the context for a specific entity.
* Valid entity types are: project, component, task, issue, milestone, decision, feature, technology, documentation, dependency.
*/
server.tool(
"loadcontext",
toolDescriptions["loadcontext"],
{
entityName: z.string(),
entityType: z.string().optional(),
sessionId: z.string().optional()
},
async ({ entityName, entityType = "project", sessionId }) => {
try {
// Validate session if ID is provided
if (sessionId) {
const sessionStates = await loadSessionStates();
if (!sessionStates.has(sessionId)) {
console.warn(`Warning: Session ${sessionId} not found, but proceeding with context load`);
// Initialize it anyway for more robustness
sessionStates.set(sessionId, []);
await saveSessionStates(sessionStates);
}
// Track that this entity was loaded in this session
const sessionState = sessionStates.get(sessionId) || [];
const loadEvent = {
type: 'context_loaded',
timestamp: new Date().toISOString(),
entityName,
entityType
};
sessionState.push(loadEvent);
sessionStates.set(sessionId, sessionState);
await saveSessionStates(sessionStates);
}
// Get entity
const entityGraph = await knowledgeGraphManager.searchNodes(entityName);
if (entityGraph.entities.length === 0) {
throw new Error(`Entity ${entityName} not found`);
}
// Find the exact entity by name (case-sensitive match)
const entity = entityGraph.entities.find(e => e.name === entityName);
if (!entity) {
throw new Error(`Entity ${entityName} not found`);
}
// Get status and priority
const status = await knowledgeGraphManager.getEntityStatus(entityName) || "unknown";
const priority = await knowledgeGraphManager.getEntityPriority(entityName);
// Format observations for display (show all observations)
const observationsList = entity.observations.length > 0
? entity.observations.map(obs => `- ${obs}`).join("\n")
: "No observations";
// Different context loading based on entity type
let contextMessage = "";
if (entityType === "project") {
// Get project status
const projectStatus = await knowledgeGraphManager.getProjectStatus(entityName);
// Format project context
const componentsText = projectStatus.components?.map((component: Entity) => {
return `- **${component.name}**${component.observations.length > 0 ? `: ${component.observations[0]}` : ''}`;
}).join("\n") || "No components found";
const featuresText = projectStatus.activeFeatures?.map((feature: Entity) => {
const featureStatus = projectStatus.statuses[feature.name] || "unknown";
return `- **${feature.name}** (${featureStatus})${feature.observations.length > 0 ? `: ${feature.observations.join(', ')}` : ''}`;
}).join("\n") || "No active features found";
const tasksText = projectStatus.activeTasks?.map((task: Entity) => {
const taskStatus = projectStatus.statuses[task.name] || "unknown";
const taskPriority = projectStatus.priorities[task.name] || "normal";
return `- **${task.name}** (${taskStatus}, ${taskPriority} priority)${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
}).join("\n") || "No active tasks found";
const issuesText = projectStatus.activeIssues?.map((issue: Entity) => {
const issueStatus = projectStatus.statuses[issue.name] || "unknown";
return `- **${issue.name}** (${issueStatus})${issue.observations.length > 0 ? `: ${issue.observations.join(', ')}` : ''}`;
}).join("\n") || "No active issues found";
const milestonesText = projectStatus.upcomingMilestones?.map((milestone: Entity) => {
const milestoneStatus = projectStatus.statuses[milestone.name] || "unknown";
return `- **${milestone.name}** (${milestoneStatus})${milestone.observations.length > 0 ? `: ${milestone.observations.join(', ')}` : ''}`;
}).join("\n") || "No upcoming milestones found";
const decisionsText = projectStatus.recentDecisions?.map((decision: Entity) => {
return `- **${decision.name}**${decision.observations.length > 0 ? `: ${decision.observations.join(', ')}` : ''}`;
}).join("\n") || "No recent decisions";
// Task sequencing information
const sequencingText = Object.keys(projectStatus.taskSequencing || {}).length > 0
? Object.entries(projectStatus.taskSequencing).map(([taskName, sequence]: [string, any]) => {
return `- **${taskName}**:\n - Precedes: ${sequence.followingTasks.length > 0 ? sequence.followingTasks.join(', ') : 'None'}\n - Follows: ${sequence.precedingTasks.length > 0 ? sequence.precedingTasks.join(', ') : 'None'}`;
}).join("\n")
: "No task sequencing information available";
contextMessage = `# Software Development Project Context: ${entityName}
## Project Overview
- **Status**: ${status}
- **Priority**: ${priority || "N/A"}
## Observations
${observationsList}
## Components
${componentsText}
## Active Features
${featuresText}
## Active Tasks
${tasksText}
## Active Issues
${issuesText}
## Upcoming Milestones
${milestonesText}
## Recent Decisions
${decisionsText}
## Task Sequencing
${sequencingText}`;
}
else if (entityType === "component") {
// Get component context
const componentContext = await knowledgeGraphManager.getComponentContext(entityName);
const projectsText = componentContext.projects?.map((project: Entity) => {
return `- **${project.name}**${project.observations.length > 0 ? `: ${project.observations.join(', ')}` : ''}`;
}).join("\n") || "No parent projects found";
const featuresText = componentContext.features?.map((feature: Entity) => {
const featureStatus = componentContext.statuses[feature.name] || "unknown";
return `- **${feature.name}** (${featureStatus})${feature.observations.length > 0 ? `: ${feature.observations.join(', ')}` : ''}`;
}).join("\n") || "No implemented features found";
const technologiesText = componentContext.technologies?.map((tech: Entity) => {
return `- **${tech.name}**${tech.observations.length > 0 ? `: ${tech.observations.join(', ')}` : ''}`;
}).join("\n") || "No technologies specified";
const issuesText = componentContext.activeIssues?.map((issue: Entity) => {
const issueStatus = componentContext.statuses[issue.name] || "unknown";
return `- **${issue.name}** (${issueStatus})${issue.observations.length > 0 ? `: ${issue.observations.join(', ')}` : ''}`;
}).join("\n") || "No active issues found";
const dependenciesText = componentContext.dependencies?.map((dep: Entity) => {
return `- **${dep.name}** (${dep.entityType})${dep.observations.length > 0 ? `: ${dep.observations.join(', ')}` : ''}`;
}).join("\n") || "No dependencies found";
const documentationText = componentContext.documentation?.map((doc: Entity) => {
return `- **${doc.name}**${doc.observations.length > 0 ? `: ${doc.observations.join(', ')}` : ''}`;
}).join("\n") || "No documentation found";
contextMessage = `# Component Context: ${entityName}
## Overview
- **Status**: ${status}
- **Priority**: ${priority || "N/A"}
## Observations
${observationsList}
## Part of Projects
${projectsText}
## Technologies
${technologiesText}
## Implemented Features
${featuresText}
## Dependencies
${dependenciesText}
## Active Issues
${issuesText}
## Documentation
${documentationText}`;
}
else if (entityType === "feature") {
// Get related entities
const relatedEntities = await knowledgeGraphManager.getRelatedEntities(entityName);
// Find implementing components
const implementingComponents = relatedEntities.incomingRelations
.filter((rel: { relation: Relation; source: Entity }) => rel.relation.relationType === "implements")
.map((rel: { relation: Relation; source: Entity }) => rel.source);
const componentsText = implementingComponents.map((component: Entity) => {
return `- **${component.name}**${component.observations.length > 0 ? `: ${component.observations.join(', ')}` : ''}`;
}).join("\n") || "No implementing components found";
// Find related tasks
const relatedTasks = [...relatedEntities.incomingRelations, ...relatedEntities.outgoingRelations]
.filter((rel: { relation: Relation; source?: Entity; target?: Entity }) =>
rel.relation.relationType === "related_to" &&
(rel.source?.entityType === "task" || rel.target?.entityType === "task")
)
.map((rel: { relation: Relation; source?: Entity; target?: Entity }) =>
rel.source?.entityType === "task" ? rel.source : rel.target
)
.filter((entity): entity is Entity => entity !== undefined);
// Get status for each task
const taskStatuses: Record<string, string> = {};
for (const task of relatedTasks) {
const taskStatus = await knowledgeGraphManager.getEntityStatus(task.name);
if (taskStatus) {
taskStatuses[task.name] = taskStatus;
}
}
const tasksText = relatedTasks.map((task: Entity) => {
const taskStatus = taskStatuses[task.name] || "unknown";
return `- **${task.name}** (${taskStatus})${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
}).join("\n") || "No related tasks found";
// Find requirements
const requirements = relatedEntities.incomingRelations
.filter((rel: { relation: Relation; source: Entity }) => rel.relation.relationType === "required_by")
.map((rel: { relation: Relation; source: Entity }) => rel.source);
const requirementsText = requirements.map((req: Entity) => {
return `- **${req.name}**${req.observations.length > 0 ? `: ${req.observations.join(', ')}` : ''}`;
}).join("\n") || "No requirements specified";
contextMessage = `# Feature Context: ${entityName}
## Overview
- **Status**: ${status}
- **Priority**: ${priority || "normal"}
## Observations
${observationsList}
## Requirements
${requirementsText}
## Implementing Components
${componentsText}
## Related Tasks
${tasksText}`;
}
else if (entityType === "task") {
// Get related entities
const relatedEntities = await knowledgeGraphManager.getRelatedEntities(entityName);
// Find related issues
const relatedIssues = relatedEntities.outgoingRelations
.filter((rel: { relation: Relation; target: Entity }) => rel.relation.relationType === "resolves")
.map((rel: { relation: Relation; target: Entity }) => rel.target);
// Get status for each issue
const issueStatuses: Record<string, string> = {};
for (const issue of relatedIssues) {
const issueStatus = await knowledgeGraphManager.getEntityStatus(issue.name);
if (issueStatus) {
issueStatuses[issue.name] = issueStatus;
}
}
const issuesText = relatedIssues.map((issue: Entity) => {
const issueStatus = issueStatuses[issue.name] || "unknown";
return `- **${issue.name}** (${issueStatus})${issue.observations.length > 0 ? `: ${issue.observations.join(', ')}` : ''}`;
}).join("\n") || "No related issues found";
// Find parent project
const parentProjects = relatedEntities.incomingRelations
.filter((rel: { relation: Relation; source: Entity }) => rel.relation.relationType === "contains" && rel.source.entityType === "project")
.map((rel: { relation: Relation; source: Entity }) => rel.source);
const projectName = parentProjects.length > 0 ? parentProjects[0].name : "Unknown project";
// Find blocking tasks or issues
const blockingItems = relatedEntities.outgoingRelations
.filter((rel: { relation: Relation; target: Entity }) => rel.relation.relationType === "blocked_by")
.map((rel: { relation: Relation; target: Entity }) => rel.target);
// Get status for each blocking item
const blockingStatuses: Record<string, string> = {};
for (const item of blockingItems) {
const itemStatus = await knowledgeGraphManager.getEntityStatus(item.name);
if (itemStatus) {
blockingStatuses[item.name] = itemStatus;
}
}
const blockingText = blockingItems.map((item: Entity) => {
const itemStatus = blockingStatuses[item.name] || "unknown";
return `- **${item.name}** (${item.entityType}, ${itemStatus})${item.observations.length > 0 ? `: ${item.observations.join(', ')}` : ''}`;
}).join("\n") || "No blocking items";
// Find task sequencing
const precedingTasks: string[] = [];
const followingTasks: string[] = [];
// Get the graph to find sequencing relations
const graph = await knowledgeGraphManager.readGraph();
for (const relation of graph.relations) {
if (relation.from === entityName && relation.relationType === 'precedes') {
followingTasks.push(relation.to);
}
if (relation.to === entityName && relation.relationType === 'precedes') {
precedingTasks.push(relation.from);
}
}
const sequencingText = `### Preceding Tasks\n${precedingTasks.length > 0 ? precedingTasks.map(t => `- ${t}`).join('\n') : 'None'}\n\n### Following Tasks\n${followingTasks.length > 0 ? followingTasks.map(t => `- ${t}`).join('\n') : 'None'}`;
contextMessage = `# Task Context: ${entityName}
## Overview
- **Project**: ${projectName}
- **Status**: ${status}
- **Priority**: ${priority || "normal"}
## Observations
${observationsList}
## Related Issues
${issuesText}
## Blocked By
${blockingText}
## Task Sequencing
${sequencingText}`;
}
else if (entityType === "milestone") {
// Get milestone progress
const milestoneProgress = await knowledgeGraphManager.getMilestoneProgress(entityName);
contextMessage = `# Milestone Context: ${entityName}
## Overview
- **Status**: ${status}
- **Progress**: ${milestoneProgress.progress?.percentage || 0}% complete
- **Complete**: ${milestoneProgress.progress?.complete ? "Yes" : "No"}
## Observations
${observationsList}
## Tasks
### Completed (${milestoneProgress.tasks?.completed?.length || 0})
${milestoneProgress.tasks?.completed?.map((task: Entity) => {
return `- **${task.name}**${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
}).join("\n") || "No completed tasks"}
### In Progress (${milestoneProgress.tasks?.inProgress?.length || 0})
${milestoneProgress.tasks?.inProgress?.map((task: Entity) => {
return `- **${task.name}**${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
}).join("\n") || "No in-progress tasks"}
### Not Started (${milestoneProgress.tasks?.notStarted?.length || 0})
${milestoneProgress.tasks?.notStarted?.map((task: Entity) => {
return `- **${task.name}**${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
}).join("\n") || "No not-started tasks"}
## Task Sequencing
${Object.keys(milestoneProgress.taskSequencing || {}).length > 0
? Object.entries(milestoneProgress.taskSequencing).map(([taskName, sequence]: [string, any]) => {
return `- **${taskName}**:\n - Precedes: ${sequence.followingTasks.length > 0 ? sequence.followingTasks.join(', ') : 'None'}\n - Follows: ${sequence.precedingTasks.length > 0 ? sequence.precedingTasks.join(', ') : 'None'}`;
}).join("\n")
: "No task sequencing information available"}`;
}
return {
content: [{
type: "text",
text: contextMessage
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : String(error)
}, null, 2)
}]
};
}
}
);
// Helper function to process each stage of endsession
async function processStage(params: {
sessionId: string;
stage: string;
stageNumber: number;
totalStages: number;
analysis?: string;
stageData?: any;
nextStageNeeded: boolean;
isRevision?: boolean;
revisesStage?: number;
}, previousStages: any[]): Promise<any> {
// Process based on the stage
switch (params.stage) {
case "summary":
// Process summary stage
return {
stage: "summary",
stageNumber: params.stageNumber,
analysis: params.analysis || "",
stageData: params.stageData || {
summary: "",
duration: "",
focus: ""
},
completed: !params.nextStageNeeded
};
case "achievements":
// Process achievements stage
return {
stage: "achievements",
stageNumber: params.stageNumber,
analysis: params.analysis || "",
stageData: params.stageData || { achievements: [] },
completed: !params.nextStageNeeded
};
case "taskUpdates":
// Process task updates stage
return {
stage: "taskUpdates",
stageNumber: params.stageNumber,
analysis: params.analysis || "",
stageData: params.stageData || { taskUpdates: [] },
completed: !params.nextStageNeeded
};
case "newTasks":
// Process new tasks stage
return {
stage: "newTasks",
stageNumber: params.stageNumber,
analysis: params.analysis || "",
stageData: params.stageData || { newTasks: [] },
completed: !params.nextStageNeeded
};
case "projectStatus":
// Process project status stage
return {
stage: "projectStatus",
stageNumber: params.stageNumber,
analysis: params.analysis || "",
stageData: params.stageData || {
projectName: "",
projectStatus: "",
projectObservation: ""
},
completed: !params.nextStageNeeded
};
case "assembly":
// Final assembly stage - compile all arguments for end session
return {
stage: "assembly",
stageNumber: params.stageNumber,
analysis: "Final assembly of endsession arguments",
stageData: assembleEndSessionArgs(previousStages),
completed: true
};
default:
throw new Error(`Unknown stage: ${params.stage}`);
}
}
// Helper function to assemble the final end session arguments
function assembleEndSessionArgs(stages: any[]): any {
const summaryStage = stages.find(s => s.stage === "summary");
const achievementsStage = stages.find(s => s.stage === "achievements");
const taskUpdatesStage = stages.find(s => s.stage === "taskUpdates");
const newTasksStage = stages.find(s => s.stage === "newTasks");
const projectStatusStage = stages.find(s => s.stage === "projectStatus");
return {
summary: summaryStage?.stageData?.summary || "",
duration: summaryStage?.stageData?.duration || "unknown",
focus: summaryStage?.stageData?.focus || "",
achievements: JSON.stringify(achievementsStage?.stageData?.achievements || []),
taskUpdates: JSON.stringify(taskUpdatesStage?.stageData?.taskUpdates || []),
projectName: projectStatusStage?.stageData?.projectName || "",
projectStatus: projectStatusStage?.stageData?.projectStatus || "",
projectObservation: projectStatusStage?.stageData?.projectObservation || "",
newTasks: JSON.stringify(newTasksStage?.stageData?.newTasks || [])
};
}
/**
* End session by processing all stages and recording the final results.
* Only use this tool if the user asks for it.
*
* Usage examples:
*
* 1. Starting the end session process with the summary stage:
* {
* "sessionId": "dev_1234567890_abc123", // From startsession
* "stage": "summary",
* "stageNumber": 1,
* "totalStages": 6, // Total stages you plan to use
* "analysis": "Analyzed progress on the authentication system",
* "stageData": {
* "summary": "Completed the login functionality and fixed related bugs",
* "duration": "3 hours",
* "focus": "AuthSystem" // Project/component name
* },
* "nextStageNeeded": true, // More stages coming
* "isRevision": false
* }
*
* 2. Middle stage for achievements:
* {
* "sessionId": "dev_1234567890_abc123",
* "stage": "achievements",
* "stageNumber": 2,
* "totalStages": 6,
* "analysis": "Listed key accomplishments",
* "stageData": {
* "achievements": [
* "Implemented password reset functionality",
* "Fixed login redirect bug",
* "Added error handling for authentication failures"
* ]
* },
* "nextStageNeeded": true,
* "isRevision": false
* }
*
* 3. Final assembly stage:
* {
* "sessionId": "dev_1234567890_abc123",
* "stage": "assembly",
* "stageNumber": 6,
* "totalStages": 6,
* "nextStageNeeded": false, // This completes the session
* "isRevision": false
* }
*/
server.tool(
"endsession",
toolDescriptions["endsession"],
{
sessionId: z.string().describe("The unique session identifier obtained from startsession"),
stage: z.string().describe("Current stage of analysis: 'summary', 'achievements', 'taskUpdates', 'newTasks', 'projectStatus', or 'assembly'"),
stageNumber: z.number().int().positive().describe("The sequence number of the current stage (starts at 1)"),
totalStages: z.number().int().positive().describe("Total number of stages in the workflow (typically 6 for standard workflow)"),
analysis: z.string().optional().describe("Text analysis or observations for the current stage"),
stageData: z.record(z.string(), z.any()).optional().describe(`Stage-specific data structure - format depends on the stage type:
- For 'summary' stage: { summary: "Session summary text", duration: "2 hours", focus: "ProjectName" }
- For 'achievements' stage: { achievements: ["Implemented feature X", "Fixed bug Y", "Refactored component Z"] }
- For 'taskUpdates' stage: { taskUpdates: [{ name: "Task1", status: "completed" }, { name: "Task2", status: "in_progress" }] }
- For 'newTasks' stage: { newTasks: [{ name: "NewTask1", description: "Implement feature A", priority: "high" }] }
- For 'projectStatus' stage: { projectName: "ProjectName", projectStatus: "in_progress", projectObservation: "Making good progress" }
- For 'assembly' stage: no stageData needed - automatic assembly of previous stages`),
nextStageNeeded: z.boolean().describe("Whether additional stages are needed after this one (false for final stage)"),
isRevision: z.boolean().optional().describe("Whether this is revising a previous stage"),
revisesStage: z.number().int().positive().optional().describe("If revising, which stage number is being revised")
},
async (params) => {
try {
// Load session states from persistent storage
const sessionStates = await loadSessionStates();
// Validate session ID
if (!sessionStates.has(params.sessionId)) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: `Session with ID ${params.sessionId} not found. Please start a new session with startsession.`
}, null, 2)
}]
};
}
// Get or initialize session state
let sessionState = sessionStates.get(params.sessionId) || [];
// Process the current stage
const stageResult = await processStage(params, sessionState);
// Store updated state
if (params.isRevision && params.revisesStage) {
// Find the analysis stages in the session state
const analysisStages = sessionState.filter(item => item.type === 'analysis_stage') || [];
if (params.revisesStage <= analysisStages.length) {
// Replace the revised stage
analysisStages[params.revisesStage - 1] = {
type: 'analysis_stage',
...stageResult
};
} else {
// Add as a new stage
analysisStages.push({
type: 'analysis_stage',
...stageResult
});
}
// Update the session state with the modified analysis stages
sessionState = [
...sessionState.filter(item => item.type !== 'analysis_stage'),
...analysisStages
];
} else {
// Add new stage
sessionState.push({
type: 'analysis_stage',
...stageResult
});
}
// Update in-memory and persistent storage
sessionStates.set(params.sessionId, sessionState);
await saveSessionStates(sessionStates);
// Check if this is the final assembly stage and no more stages are needed
if (params.stage === "assembly" && !params.nextStageNeeded) {
// Get the assembled arguments
const args = stageResult.stageData;
try {
// Parse arguments
const summary = args.summary;
const duration = args.duration;
const focus = args.focus;
const achievements = args.achievements ? JSON.parse(args.achievements) : [];
const taskUpdates = args.taskUpdates ? JSON.parse(args.taskUpdates) : [];
const projectUpdate = {
name: args.projectName,
status: args.projectStatus,
observation: args.projectObservation
};
const newTasks = args.newTasks ? JSON.parse(args.newTasks) : [];
// 2. Create achievement entities and link to focus project
const achievementEntities = achievements.map((achievement: string, i: number) => ({
name: `Achievement_${new Date().getTime()}_${i + 1}`,
entityType: "decision",
observations: [achievement]
}));
if (achievementEntities.length > 0) {
await knowledgeGraphManager.createEntities(achievementEntities);
// Link achievements to focus project
const achievementRelations = achievementEntities.map((achievement: {name: string}) => ({
from: focus,
to: achievement.name,
relationType: "contains"
}));
await knowledgeGraphManager.createRelations(achievementRelations);
}
// 3. Update task statuses
for (const task of taskUpdates) {
// First find the task entity
const taskGraph = await knowledgeGraphManager.searchNodes(`name:${task.name}`);
if (taskGraph.entities.length > 0) {
// Update the status observation
const taskEntity = taskGraph.entities[0];
// Set task status
try {
const statusValue = task.status === "completed" || task.status === "complete" ? "complete" :
task.status === "in_progress" ? "active" : "inactive";
await knowledgeGraphManager.setEntityStatus(task.name, statusValue);
} catch (error) {
console.error(`Error updating status for task ${task.name}: ${error}`);
}
// If completed, link to this session
if (task.status === "complete" || task.status === "completed") {
await knowledgeGraphManager.createRelations([{
from: focus,
to: task.name,
relationType: "resolves"
}]);
}
}
}
// 4. Update project status
const projectGraph = await knowledgeGraphManager.searchNodes(`name:${projectUpdate.name}`);
if (projectGraph.entities.length > 0) {
const projectEntity = projectGraph.entities[0];
// Add project observation if specified
if (projectUpdate.observation) {
await knowledgeGraphManager.addObservations([{
entityName: projectUpdate.name,
contents: [projectUpdate.observation]
}]);
}
// Set project status
try {
const statusValue = projectUpdate.status === "completed" || projectUpdate.status === "complete" ? "complete" :
projectUpdate.status === "in_progress" || projectUpdate.status === "active" ? "active" : "inactive";
await knowledgeGraphManager.setEntityStatus(projectUpdate.name, statusValue);
} catch (error) {
console.error(`Error updating status for project ${projectUpdate.name}: ${error}`);
}
}
// 5. Create new tasks
if (newTasks && newTasks.length > 0) {
const taskEntities = newTasks.map((task: {name: string, description: string, priority?: string, precedes?: string, follows?: string}, i: number) => ({
name: task.name,
entityType: "task",
observations: [
task.description
]
}));
await knowledgeGraphManager.createEntities(taskEntities);
// Set status, priority, and sequencing for each task
for (const task of newTasks) {
// Set task status to active by default
try {
await knowledgeGraphManager.setEntityStatus(task.name, "active");
} catch (error) {
console.error(`Error setting status for new task ${task.name}: ${error}`);
}
// Set task priority if specified
if (task.priority) {
try {
const priorityValue = task.priority.toLowerCase() === "high" ? "high" : "low";
await knowledgeGraphManager.setEntityPriority(task.name, priorityValue);
} catch (error) {
console.error(`Error setting priority for new task ${task.name}: ${error}`);
}
}
// Create sequencing relationships if specified
try {
// This task precedes another task
if (task.precedes) {
await knowledgeGraphManager.createRelations([{
from: task.name,
to: task.precedes,
relationType: "precedes"
}]);
}
// This task follows another task
if (task.follows) {
await knowledgeGraphManager.createRelations([{
from: task.follows,
to: task.name,
relationType: "precedes"
}]);
}
} catch (error) {
console.error(`Error setting sequencing for task ${task.name}: ${error}`);
}
}
// Link tasks to project
const taskRelations = taskEntities.map((task: {name: string}) => ({
from: projectUpdate.name,
to: task.name,
relationType: "contains"
}));
await knowledgeGraphManager.createRelations(taskRelations);
}
// Record session completion in persistent storage
sessionState.push({
type: 'session_completed',
timestamp: new Date().toISOString(),
summary: summary,
project: focus
});
sessionStates.set(params.sessionId, sessionState);
await saveSessionStates(sessionStates);
// Prepare the summary message
const summaryMessage = `# Development Session Recorded
I've recorded your development session focusing on ${focus}.
## Achievements Documented
${achievements.map((a: string) => `- ${a}`).join('\n') || "No achievements recorded."}
## Task Updates
${taskUpdates.map((t: {name: string, status: string}) => `- ${t.name}: ${t.status}`).join('\n') || "No task updates."}
## Project Status
Project ${projectUpdate.name} has been updated to: ${projectUpdate.status}
${newTasks && newTasks.length > 0 ? `## New Tasks Added
${newTasks.map((t: {name: string, description: string, priority?: string}) => `- ${t.name}: ${t.description} (Priority: ${t.priority || "medium"})`).join('\n')}` : "No new tasks added."}
## Session Summary
${summary}
Would you like me to perform any additional updates to the development knowledge graph?`;
// Return the final result with the session recorded message
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
stageCompleted: params.stage,
nextStageNeeded: false,
stageResult: stageResult,
sessionRecorded: true,
summaryMessage: summaryMessage
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: `Error recording development session: ${error instanceof Error ? error.message : String(error)}`
}, null, 2)
}]
};
}
} else {
// This is not the final stage or more stages are needed
// Return intermediate result
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
stageCompleted: params.stage,
nextStageNeeded: params.nextStageNeeded,
stageResult: stageResult,
endSessionArgs: params.stage === "assembly" ? stageResult.stageData : null
}, null, 2)
}]
};
}
} catch (error) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : String(error)
}, null, 2)
}]
};
}
}
);
// Connect the server to the transport
const transport = new StdioServerTransport();
await server.connect(transport);
} catch (error) {
console.error("Error starting server:", error);
process.exit(1);
}
}
// Run the main function
main().catch(error => {
console.error("Unhandled error:", error);
process.exit(1);
});
// Export the KnowledgeGraphManager for testing
export { KnowledgeGraphManager };
```