index.ts•57.5 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { analyzeRepository } from './tools/analyze-repository.js';
import { recommendSSG } from './tools/recommend-ssg.js';
import { generateConfig } from './tools/generate-config.js';
import { setupStructure } from './tools/setup-structure.js';
import { deployPages } from './tools/deploy-pages.js';
import { verifyDeployment } from './tools/verify-deployment.js';
import { handlePopulateDiataxisContent } from './tools/populate-content.js';
import { handleValidateDiataxisContent, validateGeneralContent } from './tools/validate-content.js';
import { handleUpdateExistingDocumentation } from './tools/update-existing-documentation.js';
import { detectDocumentationGaps } from './tools/detect-gaps.js';
import { testLocalDeployment } from './tools/test-local-deployment.js';
import { evaluateReadmeHealth } from './tools/evaluate-readme-health.js';
import { readmeBestPractices } from './tools/readme-best-practices.js';
import { checkDocumentationLinks } from './tools/check-documentation-links.js';
import { generateReadmeTemplate } from './tools/generate-readme-template.js';
import { validateReadmeChecklist } from './tools/validate-readme-checklist.js';
import { analyzeReadme } from './tools/analyze-readme.js';
import { optimizeReadme } from './tools/optimize-readme.js';
import { formatMCPResponse } from './types/api.js';
import { generateTechnicalWriterPrompts } from './prompts/technical-writer-prompts.js';
import {
DOCUMENTATION_WORKFLOWS,
WORKFLOW_EXECUTION_GUIDANCE,
WORKFLOW_METADATA,
} from './workflows/documentation-workflow.js';
import {
initializeMemory,
rememberAnalysis,
rememberRecommendation,
getProjectInsights,
getSimilarProjects,
getMemoryStatistics,
exportMemories,
cleanupOldMemories,
memoryTools,
} from './memory/index.js';
// Get version from package.json
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
const server = new Server(
{
name: 'documcp',
version: packageJson.version,
},
{
capabilities: {
tools: {},
prompts: {
listChanged: true,
},
resources: {
subscribe: true,
listChanged: true,
},
},
},
);
// Tool definitions following ADR-006
const TOOLS = [
{
name: 'analyze_repository',
description: 'Analyze repository structure, dependencies, and documentation needs',
inputSchema: z.object({
path: z.string().describe('Path to the repository to analyze'),
depth: z.enum(['quick', 'standard', 'deep']).optional().default('standard'),
}),
},
{
name: 'recommend_ssg',
description: 'Recommend the best static site generator based on project analysis',
inputSchema: z.object({
analysisId: z.string().describe('ID from previous repository analysis'),
preferences: z
.object({
priority: z.enum(['simplicity', 'features', 'performance']).optional(),
ecosystem: z.enum(['javascript', 'python', 'ruby', 'go', 'any']).optional(),
})
.optional(),
}),
},
{
name: 'generate_config',
description: 'Generate configuration files for the selected static site generator',
inputSchema: z.object({
ssg: z.enum(['jekyll', 'hugo', 'docusaurus', 'mkdocs', 'eleventy']),
projectName: z.string(),
projectDescription: z.string().optional(),
outputPath: z.string().describe('Where to generate config files'),
}),
},
{
name: 'setup_structure',
description: 'Create Diataxis-compliant documentation structure',
inputSchema: z.object({
path: z.string().describe('Root path for documentation'),
ssg: z.enum(['jekyll', 'hugo', 'docusaurus', 'mkdocs', 'eleventy']),
includeExamples: z.boolean().optional().default(true),
}),
},
{
name: 'deploy_pages',
description: 'Set up GitHub Pages deployment workflow',
inputSchema: z.object({
repository: z.string().describe('Repository path or URL'),
ssg: z.enum(['jekyll', 'hugo', 'docusaurus', 'mkdocs', 'eleventy']),
branch: z.string().optional().default('gh-pages'),
customDomain: z.string().optional(),
}),
},
{
name: 'verify_deployment',
description: 'Verify and troubleshoot GitHub Pages deployment',
inputSchema: z.object({
repository: z.string().describe('Repository path or URL'),
url: z.string().optional().describe('Expected deployment URL'),
}),
},
{
name: 'populate_diataxis_content',
description: 'Intelligently populate Diataxis documentation with project-specific content',
inputSchema: z.object({
analysisId: z.string().describe('Repository analysis ID from analyze_repository tool'),
docsPath: z.string().describe('Path to documentation directory'),
populationLevel: z
.enum(['basic', 'comprehensive', 'intelligent'])
.optional()
.default('comprehensive'),
includeProjectSpecific: z.boolean().optional().default(true),
preserveExisting: z.boolean().optional().default(true),
technologyFocus: z
.array(z.string())
.optional()
.describe('Specific technologies to emphasize'),
}),
},
{
name: 'update_existing_documentation',
description:
'Intelligently analyze and update existing documentation using memory insights and code comparison',
inputSchema: z.object({
analysisId: z.string().describe('Repository analysis ID from analyze_repository tool'),
docsPath: z.string().describe('Path to existing documentation directory'),
compareMode: z
.enum(['comprehensive', 'gap-detection', 'accuracy-check'])
.optional()
.default('comprehensive')
.describe('Mode of comparison between code and documentation'),
updateStrategy: z
.enum(['conservative', 'moderate', 'aggressive'])
.optional()
.default('moderate')
.describe('How aggressively to suggest updates'),
preserveStyle: z
.boolean()
.optional()
.default(true)
.describe('Preserve existing documentation style and formatting'),
focusAreas: z
.array(z.string())
.optional()
.describe('Specific areas to focus updates on (e.g., "dependencies", "scripts", "api")'),
}),
},
{
name: 'validate_diataxis_content',
description:
'Validate the accuracy, completeness, and compliance of generated Diataxis documentation',
inputSchema: z.object({
contentPath: z.string().describe('Path to the documentation directory to validate'),
analysisId: z
.string()
.optional()
.describe('Optional repository analysis ID for context-aware validation'),
validationType: z
.enum(['accuracy', 'completeness', 'compliance', 'all'])
.optional()
.default('all')
.describe('Type of validation: accuracy, completeness, compliance, or all'),
includeCodeValidation: z
.boolean()
.optional()
.default(true)
.describe('Whether to validate code examples'),
confidence: z
.enum(['strict', 'moderate', 'permissive'])
.optional()
.default('moderate')
.describe('Validation confidence level: strict, moderate, or permissive'),
}),
},
{
name: 'validate_content',
description:
'Validate general content quality: broken links, code syntax, references, and basic accuracy',
inputSchema: z.object({
contentPath: z.string().describe('Path to the content directory to validate'),
validationType: z
.string()
.optional()
.default('all')
.describe('Type of validation: links, code, references, or all'),
includeCodeValidation: z
.boolean()
.optional()
.default(true)
.describe('Whether to validate code blocks'),
followExternalLinks: z
.boolean()
.optional()
.default(false)
.describe('Whether to validate external URLs (slower)'),
}),
},
{
name: 'detect_documentation_gaps',
description:
'Analyze repository and existing documentation to identify missing content and gaps',
inputSchema: z.object({
repositoryPath: z.string().describe('Path to the repository to analyze'),
documentationPath: z.string().optional().describe('Path to existing documentation (if any)'),
analysisId: z.string().optional().describe('Optional existing analysis ID to reuse'),
depth: z.enum(['quick', 'standard', 'comprehensive']).optional().default('standard'),
}),
},
{
name: 'test_local_deployment',
description: 'Test documentation build and local server before deploying to GitHub Pages',
inputSchema: z.object({
repositoryPath: z.string().describe('Path to the repository'),
ssg: z.enum(['jekyll', 'hugo', 'docusaurus', 'mkdocs', 'eleventy']),
port: z.number().optional().default(3000).describe('Port for local server'),
timeout: z.number().optional().default(60).describe('Timeout in seconds for build process'),
skipBuild: z
.boolean()
.optional()
.default(false)
.describe('Skip build step and only start server'),
}),
},
{
name: 'evaluate_readme_health',
description:
'Evaluate README files for community health, accessibility, and onboarding effectiveness',
inputSchema: z.object({
readme_path: z.string().describe('Path to the README file to evaluate'),
project_type: z
.enum(['community_library', 'enterprise_tool', 'personal_project', 'documentation'])
.optional()
.default('community_library')
.describe('Type of project for tailored evaluation'),
repository_path: z
.string()
.optional()
.describe('Optional path to repository for additional context'),
}),
},
{
name: 'readme_best_practices',
description:
'Analyze README files against best practices checklist and generate templates for improvement',
inputSchema: z.object({
readme_path: z.string().describe('Path to the README file to analyze'),
project_type: z
.enum(['library', 'application', 'tool', 'documentation', 'framework'])
.optional()
.default('library')
.describe('Type of project for tailored analysis'),
generate_template: z
.boolean()
.optional()
.default(false)
.describe('Generate README templates and community files'),
output_directory: z
.string()
.optional()
.describe('Directory to write generated templates and community files'),
include_community_files: z
.boolean()
.optional()
.default(true)
.describe('Generate community health files (CONTRIBUTING.md, CODE_OF_CONDUCT.md, etc.)'),
target_audience: z
.enum(['beginner', 'intermediate', 'advanced', 'mixed'])
.optional()
.default('mixed')
.describe('Target audience for recommendations'),
}),
},
{
name: 'check_documentation_links',
description:
'Comprehensive link checking for documentation deployment with external, internal, and anchor link validation',
inputSchema: z.object({
documentation_path: z
.string()
.optional()
.default('./docs')
.describe('Path to the documentation directory to check'),
check_external_links: z
.boolean()
.optional()
.default(true)
.describe('Validate external URLs (slower but comprehensive)'),
check_internal_links: z
.boolean()
.optional()
.default(true)
.describe('Validate internal file references'),
check_anchor_links: z
.boolean()
.optional()
.default(true)
.describe('Validate anchor links within documents'),
timeout_ms: z
.number()
.min(1000)
.max(30000)
.optional()
.default(5000)
.describe('Timeout for external link requests in milliseconds'),
max_concurrent_checks: z
.number()
.min(1)
.max(20)
.optional()
.default(5)
.describe('Maximum concurrent link checks'),
allowed_domains: z
.array(z.string())
.optional()
.default([])
.describe('Whitelist of allowed external domains (empty = all allowed)'),
ignore_patterns: z
.array(z.string())
.optional()
.default([])
.describe('URL patterns to ignore during checking'),
fail_on_broken_links: z
.boolean()
.optional()
.default(false)
.describe('Fail the check if broken links are found'),
output_format: z
.enum(['summary', 'detailed', 'json'])
.optional()
.default('detailed')
.describe('Output format for results'),
}),
},
{
name: 'generate_readme_template',
description:
'Generate standardized README templates for different project types with best practices',
inputSchema: z.object({
projectName: z.string().min(1).describe('Name of the project'),
description: z.string().min(1).describe('Brief description of what the project does'),
templateType: z
.enum(['library', 'application', 'cli-tool', 'api', 'documentation'])
.describe('Type of project template to generate'),
author: z.string().optional().describe('Project author/organization name'),
license: z.string().optional().default('MIT').describe('Project license'),
includeScreenshots: z
.boolean()
.optional()
.default(false)
.describe('Include screenshot placeholders for applications'),
includeBadges: z.boolean().optional().default(true).describe('Include status badges'),
includeContributing: z
.boolean()
.optional()
.default(true)
.describe('Include contributing section'),
outputPath: z.string().optional().describe('Path to write the generated README.md file'),
}),
},
{
name: 'validate_readme_checklist',
description:
'Validate README files against community best practices checklist with detailed scoring',
inputSchema: z.object({
readmePath: z.string().min(1).describe('Path to the README file to validate'),
projectPath: z
.string()
.optional()
.describe('Path to project directory for additional context'),
strict: z.boolean().optional().default(false).describe('Use strict validation rules'),
outputFormat: z
.enum(['json', 'markdown', 'console'])
.optional()
.default('console')
.describe('Output format for the validation report'),
}),
},
{
name: 'analyze_readme',
description:
'Comprehensive README analysis with length assessment, structure evaluation, and optimization opportunities',
inputSchema: z.object({
project_path: z.string().min(1).describe('Path to the project directory containing README'),
target_audience: z
.enum(['community_contributors', 'enterprise_users', 'developers', 'general'])
.optional()
.default('community_contributors')
.describe('Target audience for analysis'),
optimization_level: z
.enum(['light', 'moderate', 'aggressive'])
.optional()
.default('moderate')
.describe('Level of optimization suggestions'),
max_length_target: z
.number()
.min(50)
.max(1000)
.optional()
.default(300)
.describe('Target maximum length in lines'),
}),
},
{
name: 'optimize_readme',
description:
'Optimize README content by restructuring, condensing, and extracting detailed sections to separate documentation',
inputSchema: z.object({
readme_path: z.string().min(1).describe('Path to the README file to optimize'),
strategy: z
.enum(['community_focused', 'enterprise_focused', 'developer_focused', 'general'])
.optional()
.default('community_focused')
.describe('Optimization strategy'),
max_length: z
.number()
.min(50)
.max(1000)
.optional()
.default(300)
.describe('Target maximum length in lines'),
include_tldr: z
.boolean()
.optional()
.default(true)
.describe('Generate and include TL;DR section'),
preserve_existing: z
.boolean()
.optional()
.default(true)
.describe('Preserve existing content structure where possible'),
output_path: z
.string()
.optional()
.describe('Path to write optimized README (if not specified, returns content only)'),
create_docs_directory: z
.boolean()
.optional()
.default(true)
.describe('Create docs/ directory for extracted content'),
}),
},
// Memory system tools
...memoryTools.map((tool) => ({
...tool,
inputSchema: z.object(
Object.entries(tool.inputSchema.properties || {}).reduce(
(acc: any, [key, value]: [string, any]) => {
if (value.type === 'string') {
acc[key] = value.enum ? z.enum(value.enum) : z.string();
} else if (value.type === 'number') {
acc[key] = z.number();
} else if (value.type === 'boolean') {
acc[key] = z.boolean();
} else if (value.type === 'object') {
acc[key] = z.object({});
}
if (value.description) {
acc[key] = acc[key].describe(value.description);
}
if (!tool.inputSchema.required?.includes(key)) {
acc[key] = acc[key].optional();
}
if (value.default !== undefined) {
acc[key] = acc[key].default(value.default);
}
return acc;
},
{},
),
),
})),
];
// Native MCP Prompts for technical writing assistance
const PROMPTS = [
{
name: 'tutorial-writer',
description: 'Generate learning-oriented tutorial content following Diataxis principles',
arguments: [
{ name: 'project_path', description: 'Path to the project directory', required: true },
{ name: 'target_audience', description: 'Target audience for the tutorial', required: false },
{ name: 'learning_goal', description: 'What users should learn', required: false },
],
},
{
name: 'howto-guide-writer',
description: 'Generate problem-oriented how-to guide content following Diataxis principles',
arguments: [
{ name: 'project_path', description: 'Path to the project directory', required: true },
{ name: 'problem', description: 'Problem to solve', required: false },
{ name: 'user_experience', description: 'User experience level', required: false },
],
},
{
name: 'reference-writer',
description:
'Generate information-oriented reference documentation following Diataxis principles',
arguments: [
{ name: 'project_path', description: 'Path to the project directory', required: true },
{
name: 'reference_type',
description: 'Type of reference (API, CLI, etc.)',
required: false,
},
{ name: 'completeness', description: 'Level of completeness required', required: false },
],
},
{
name: 'explanation-writer',
description:
'Generate understanding-oriented explanation content following Diataxis principles',
arguments: [
{ name: 'project_path', description: 'Path to the project directory', required: true },
{ name: 'concept', description: 'Concept to explain', required: false },
{ name: 'depth', description: 'Depth of explanation', required: false },
],
},
{
name: 'diataxis-organizer',
description: 'Organize existing documentation using Diataxis framework principles',
arguments: [
{ name: 'project_path', description: 'Path to the project directory', required: true },
{
name: 'current_docs',
description: 'Description of current documentation',
required: false,
},
{ name: 'priority', description: 'Organization priority', required: false },
],
},
{
name: 'readme-optimizer',
description: 'Optimize README content using Diataxis-aware principles',
arguments: [
{ name: 'project_path', description: 'Path to the project directory', required: true },
{ name: 'optimization_focus', description: 'Focus area for optimization', required: false },
],
},
// Guided workflow prompts (ADR-007)
{
name: 'analyze-and-recommend',
description: 'Complete repository analysis and SSG recommendation workflow',
arguments: [
{ name: 'project_path', description: 'Path to the project directory', required: true },
{
name: 'analysis_depth',
description: 'Analysis depth: quick, standard, deep',
required: false,
},
{
name: 'preferences',
description: 'SSG preferences (ecosystem, priority)',
required: false,
},
],
},
{
name: 'setup-documentation',
description: 'Create comprehensive documentation structure with best practices',
arguments: [
{ name: 'project_path', description: 'Path to the project directory', required: true },
{ name: 'ssg_type', description: 'Static site generator type', required: false },
{ name: 'include_examples', description: 'Include example content', required: false },
],
},
{
name: 'troubleshoot-deployment',
description: 'Diagnose and fix GitHub Pages deployment issues',
arguments: [
{ name: 'repository', description: 'Repository path or URL', required: true },
{ name: 'deployment_url', description: 'Expected deployment URL', required: false },
{ name: 'issue_description', description: 'Description of the issue', required: false },
],
},
];
// In-memory storage for resources
const resourceStore = new Map<string, { content: string; mimeType: string }>();
// Helper function to store tool results as resources
function storeResourceFromToolResult(
toolName: string,
args: any,
result: any,
id?: string,
): string {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const resourceId = id || `${timestamp}-${Math.random().toString(36).substring(2, 11)}`;
let uri: string;
let mimeType = 'application/json';
let content: string;
// Determine URI and content based on tool type
switch (toolName) {
case 'analyze_repository':
uri = `documcp://analysis/${resourceId}`;
content = JSON.stringify(result, null, 2);
break;
case 'recommend_ssg':
uri = `documcp://recommendations/${resourceId}`;
content = JSON.stringify(result, null, 2);
break;
case 'generate_config':
uri = `documcp://config/${args.ssg}/${resourceId}`;
mimeType = 'text/plain';
content = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
break;
case 'setup_structure':
uri = `documcp://structure/${resourceId}`;
content = JSON.stringify(result, null, 2);
break;
case 'deploy_pages':
uri = `documcp://deployment/${resourceId}`;
mimeType = 'text/yaml';
content = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
break;
case 'verify_deployment':
uri = `documcp://verification/${resourceId}`;
content = JSON.stringify(result, null, 2);
break;
default:
uri = `documcp://results/${toolName}/${resourceId}`;
content = JSON.stringify(result, null, 2);
}
// Store the resource
resourceStore.set(uri, { content, mimeType });
return uri;
}
// Resource definitions following ADR-007
const RESOURCES = [
{
uri: 'documcp://analysis/',
name: 'Repository Analysis Results',
description: 'Results from repository analysis operations',
mimeType: 'application/json',
},
{
uri: 'documcp://recommendations/',
name: 'SSG Recommendations',
description: 'Static Site Generator recommendations based on project analysis',
mimeType: 'application/json',
},
{
uri: 'documcp://config/',
name: 'Generated Configuration Files',
description: 'Generated SSG configuration files',
mimeType: 'text/plain',
},
{
uri: 'documcp://structure/',
name: 'Documentation Structure Templates',
description: 'Diataxis-compliant documentation structures',
mimeType: 'application/json',
},
{
uri: 'documcp://deployment/',
name: 'GitHub Actions Workflows',
description: 'Generated deployment workflows',
mimeType: 'text/yaml',
},
{
uri: 'documcp://verification/',
name: 'Deployment Verification Results',
description: 'Results from deployment verification checks',
mimeType: 'application/json',
},
{
uri: 'documcp://templates/',
name: 'Reusable Templates',
description: 'Template files for documentation setup',
mimeType: 'text/plain',
},
{
uri: 'documcp://workflows/',
name: 'Documentation Workflows',
description: 'Guided workflows for different documentation scenarios',
mimeType: 'application/json',
},
{
uri: 'documcp://results/',
name: 'Tool Results',
description: 'Results from various DocuMCP tools',
mimeType: 'application/json',
},
];
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: TOOLS.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: zodToJsonSchema(tool.inputSchema),
})),
}));
// List available prompts
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: PROMPTS,
}));
// Get specific prompt
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Generate dynamic prompt messages using our Diataxis-aligned prompt system
const projectPath = args?.project_path || process.cwd();
const messages = await generateTechnicalWriterPrompts(name, projectPath, args || {});
return {
description: `Technical writing assistance for ${name}`,
messages,
};
});
// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: RESOURCES,
}));
// Read specific resource
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
// Check if resource exists in store
const resource = resourceStore.get(uri);
if (resource) {
return {
contents: [
{
uri,
mimeType: resource.mimeType,
text: resource.content,
},
],
};
}
// Handle template resources (static content)
if (uri.startsWith('documcp://templates/')) {
const templateType = uri.split('/').pop();
switch (templateType) {
case 'jekyll-config':
return {
contents: [
{
uri,
mimeType: 'text/yaml',
text: `# Jekyll Configuration Template
title: "Documentation Site"
description: "Project documentation"
baseurl: ""
url: ""
markdown: kramdown
highlighter: rouge
theme: minima
plugins:
- jekyll-feed
- jekyll-sitemap
exclude:
- Gemfile
- Gemfile.lock
- node_modules
- vendor
`,
},
],
};
case 'hugo-config':
return {
contents: [
{
uri,
mimeType: 'text/yaml',
text: `# Hugo Configuration Template
baseURL: "https://username.github.io/repository"
languageCode: "en-us"
title: "Documentation Site"
theme: "docsy"
params:
github_repo: "https://github.com/username/repository"
github_branch: "main"
markup:
goldmark:
renderer:
unsafe: true
highlight:
style: github
lineNos: true
`,
},
],
};
case 'diataxis-structure':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
structure: {
tutorials: {
description: 'Learning-oriented guides',
files: ['getting-started.md', 'your-first-project.md'],
},
'how-to-guides': {
description: 'Problem-oriented step-by-step guides',
files: ['common-tasks.md', 'troubleshooting.md'],
},
reference: {
description: 'Information-oriented technical reference',
files: ['api-reference.md', 'configuration.md'],
},
explanation: {
description: 'Understanding-oriented background material',
files: ['architecture.md', 'design-decisions.md'],
},
},
},
null,
2,
),
},
],
};
default:
throw new Error(`Unknown template: ${templateType}`);
}
}
// Handle workflow resources
if (uri.startsWith('documcp://workflows/')) {
const workflowType = uri.split('/').pop();
switch (workflowType) {
case 'all':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
workflows: DOCUMENTATION_WORKFLOWS,
executionGuidance: WORKFLOW_EXECUTION_GUIDANCE,
metadata: WORKFLOW_METADATA,
},
null,
2,
),
},
],
};
case 'quick-setup':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(DOCUMENTATION_WORKFLOWS['quick-documentation-setup'], null, 2),
},
],
};
case 'full-setup':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(DOCUMENTATION_WORKFLOWS['full-documentation-setup'], null, 2),
},
],
};
case 'guidance':
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
executionGuidance: WORKFLOW_EXECUTION_GUIDANCE,
recommendationEngine:
'Use recommendWorkflow() function with project status and requirements',
},
null,
2,
),
},
],
};
default: {
// Try to find specific workflow
const workflow = DOCUMENTATION_WORKFLOWS[workflowType || ''];
if (workflow) {
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(workflow, null, 2),
},
],
};
}
throw new Error(`Unknown workflow: ${workflowType}`);
}
}
}
throw new Error(`Resource not found: ${uri}`);
});
// Helper function to store resources
function storeResource(uri: string, content: string, mimeType: string): void {
resourceStore.set(uri, { content, mimeType });
}
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'analyze_repository': {
const result = await analyzeRepository(args);
// Store analysis result as resource
const resourceUri = storeResourceFromToolResult('analyze_repository', args, result);
(result as any).resourceUri = resourceUri;
// Remember in persistent memory
if (args?.path && typeof args.path === 'string') {
const memoryId = await rememberAnalysis(args.path, result);
(result as any).memoryId = memoryId;
// Get insights from similar projects
const similarProjects = await getSimilarProjects(result, 3);
if (similarProjects.length > 0) {
(result as any).insights = {
similarProjects,
message: `Found ${similarProjects.length} similar projects in memory`,
};
}
}
return result;
}
case 'recommend_ssg': {
const result = await recommendSSG(args);
// Store recommendation as resource
const resourceUri = storeResourceFromToolResult('recommend_ssg', args, result);
(result as any).resourceUri = resourceUri;
// Remember recommendation
if (args?.analysisId && typeof args.analysisId === 'string') {
const memoryId = await rememberRecommendation(args.analysisId, result);
(result as any).memoryId = memoryId;
// Get project history if available
const projectInsights = await getProjectInsights(args.analysisId);
if (projectInsights.length > 0) {
(result as any).projectHistory = projectInsights;
}
}
return result;
}
case 'generate_config': {
const result = await generateConfig(args);
// Store generated config as resource
const resourceUri = storeResourceFromToolResult('generate_config', args, result);
(result as any).resourceUri = resourceUri;
return result;
}
case 'setup_structure': {
const result = await setupStructure(args);
// Store structure as resource
const resourceUri = storeResourceFromToolResult('setup_structure', args, result);
(result as any).resourceUri = resourceUri;
return result;
}
case 'deploy_pages': {
const result = await deployPages(args);
// Store deployment workflow as resource
const resourceUri = storeResourceFromToolResult('deploy_pages', args, result);
(result as any).resourceUri = resourceUri;
return result;
}
case 'verify_deployment': {
const result = await verifyDeployment(args);
// Store verification result as resource
const resourceUri = storeResourceFromToolResult('verify_deployment', args, result);
(result as any).resourceUri = resourceUri;
return result;
}
case 'populate_diataxis_content': {
const result = await handlePopulateDiataxisContent(args);
// Store populated content info as resource
const populationId = `population-${Date.now()}`;
storeResource(
`documcp://structure/${populationId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return {
content: [
{
type: 'text',
text: `Content population completed successfully. Generated ${
result.filesCreated
} files with ${Math.round(result.populationMetrics.coverage)}% coverage.`,
},
{
type: 'text',
text: `Population metrics: Coverage: ${result.populationMetrics.coverage}%, Completeness: ${result.populationMetrics.completeness}%, Project Specificity: ${result.populationMetrics.projectSpecificity}%`,
},
{
type: 'text',
text: `Next steps:\n${result.nextSteps.map((step) => `- ${step}`).join('\n')}`,
},
],
};
}
case 'update_existing_documentation': {
const result = await handleUpdateExistingDocumentation(args);
// Store update analysis as resource
const updateId = `update-${Date.now()}`;
storeResource(
`documcp://analysis/${updateId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return {
content: [
{
type: 'text',
text: `Documentation analysis completed. Found ${result.updateMetrics.gapsDetected} gaps and generated ${result.updateMetrics.recommendationsGenerated} recommendations.`,
},
{
type: 'text',
text: `Update metrics: Confidence Score: ${result.updateMetrics.confidenceScore}, Estimated Effort: ${result.updateMetrics.estimatedEffort}`,
},
{
type: 'text',
text: `Memory insights: ${result.memoryInsights.similarProjects.length} similar projects analyzed, ${result.memoryInsights.successfulUpdatePatterns.length} successful update patterns found`,
},
{
type: 'text',
text: `Top recommendations:\n${result.recommendations
.slice(0, 5)
.map(
(rec, i) =>
`${i + 1}. ${rec.reasoning} (confidence: ${Math.round(rec.confidence * 100)}%)`,
)
.join('\n')}`,
},
{
type: 'text',
text: `Next steps:\n${result.nextSteps.map((step) => `- ${step}`).join('\n')}`,
},
],
};
}
case 'validate_diataxis_content': {
const result = await handleValidateDiataxisContent(args);
// Store validation results as resource
const validationId = `validation-${Date.now()}`;
storeResource(
`documcp://analysis/${validationId}`,
JSON.stringify(result, null, 2),
'application/json',
);
// Return structured validation results as JSON
const validationSummary = {
status: result.success ? 'PASSED' : 'ISSUES FOUND',
confidence: `${result.confidence.overall}%`,
issuesFound: result.issues.length,
breakdown: {
errors: result.issues.filter((i) => i.type === 'error').length,
warnings: result.issues.filter((i) => i.type === 'warning').length,
info: result.issues.filter((i) => i.type === 'info').length,
},
topIssues: result.issues.slice(0, 5).map((issue) => ({
type: issue.type.toUpperCase(),
category: issue.category,
file: issue.location.file,
description: issue.description,
})),
recommendations: result.recommendations,
nextSteps: result.nextSteps,
confidenceBreakdown: result.confidence.breakdown,
resourceId: validationId,
};
return {
content: [
{
type: 'text',
text: `Content validation ${
result.success ? 'passed' : 'found issues'
}. Overall confidence: ${result.confidence.overall}%.`,
},
{
type: 'text',
text: `Issues found: ${result.issues.length} (${
result.issues.filter((i) => i.type === 'error').length
} errors, ${result.issues.filter((i) => i.type === 'warning').length} warnings)`,
},
{
type: 'text',
text: JSON.stringify(validationSummary, null, 2),
},
],
};
}
case 'validate_content': {
const result = await validateGeneralContent(args);
// Store validation results as resource
const validationId = `content-validation-${Date.now()}`;
storeResource(
`documcp://analysis/${validationId}`,
JSON.stringify(result, null, 2),
'application/json',
);
// Return structured validation results as JSON
const contentSummary = {
status: result.success ? 'PASSED' : 'ISSUES FOUND',
summary: result.summary,
linksChecked: result.linksChecked || 0,
codeBlocksValidated: result.codeBlocksValidated || 0,
brokenLinks: result.brokenLinks || [],
codeErrors: (result.codeErrors || []).slice(0, 10), // Limit to first 10 errors
recommendations: result.recommendations || [],
resourceId: validationId,
};
return {
content: [
{
type: 'text',
text: `Content validation completed. Status: ${
result.success ? 'PASSED' : 'ISSUES FOUND'
}`,
},
{
type: 'text',
text: `Results: ${result.linksChecked || 0} links checked, ${
result.codeBlocksValidated || 0
} code blocks validated`,
},
{
type: 'text',
text: JSON.stringify(contentSummary, null, 2),
},
],
};
}
case 'detect_documentation_gaps': {
const result = await detectDocumentationGaps(args);
// Store gap analysis as resource
const gapAnalysisId = `gaps-${Date.now()}`;
storeResource(
`documcp://analysis/${gapAnalysisId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return result;
}
case 'test_local_deployment': {
const result = await testLocalDeployment(args);
// Store test results as resource
const testId = `test-${args?.ssg || 'unknown'}-${Date.now()}`;
storeResource(
`documcp://deployment/${testId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return result;
}
case 'evaluate_readme_health': {
const result = await evaluateReadmeHealth(args as any);
// Store health evaluation as resource
const healthId = `readme-health-${Date.now()}`;
storeResource(
`documcp://analysis/${healthId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return result;
}
case 'readme_best_practices': {
const result = await readmeBestPractices(args as any);
// Store best practices analysis as resource
const analysisId = `readme-best-practices-${Date.now()}`;
storeResource(
`documcp://analysis/${analysisId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return formatMCPResponse(result);
}
case 'check_documentation_links': {
const result = await checkDocumentationLinks(args as any);
// Store link check results as resource
const linkCheckId = `link-check-${Date.now()}`;
storeResource(
`documcp://analysis/${linkCheckId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return formatMCPResponse(result);
}
case 'generate_readme_template': {
const result = await generateReadmeTemplate(args as any);
// Store generated template as resource
const templateId = `readme-template-${Date.now()}`;
storeResource(`documcp://template/${templateId}`, result.content, 'text/markdown');
return formatMCPResponse({
success: true,
data: result,
metadata: {
toolVersion: packageJson.version,
executionTime: Date.now(),
timestamp: new Date().toISOString(),
},
});
}
case 'validate_readme_checklist': {
const result = await validateReadmeChecklist(args as any);
// Store validation report as resource
const validationId = `readme-validation-${Date.now()}`;
storeResource(
`documcp://analysis/${validationId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return formatMCPResponse({
success: true,
data: result,
metadata: {
toolVersion: packageJson.version,
executionTime: Date.now(),
timestamp: new Date().toISOString(),
},
});
}
case 'analyze_readme': {
const result = await analyzeReadme(args as any);
// Store analysis results as resource
const analysisId = `readme-analysis-${Date.now()}`;
storeResource(
`documcp://analysis/${analysisId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return formatMCPResponse(result);
}
case 'optimize_readme': {
const result = await optimizeReadme(args as any);
// Store optimization results as resource
const optimizationId = `readme-optimization-${Date.now()}`;
storeResource(
`documcp://analysis/${optimizationId}`,
JSON.stringify(result, null, 2),
'application/json',
);
return formatMCPResponse(result);
}
// Memory system tools
case 'memory_recall': {
await initializeMemory(); // Ensure memory is initialized
const manager = (await import('./memory/index.js')).getMemoryManager();
if (!manager) throw new Error('Memory system not initialized');
let results;
if (args?.type === 'all') {
results = await manager.search(args?.query || '', {
sortBy: 'timestamp',
});
} else {
results = await manager.search(args?.type || 'analysis', { sortBy: 'timestamp' });
}
if (args?.limit && typeof args.limit === 'number') {
results = results.slice(0, args.limit);
}
return {
content: [
{
type: 'text',
text: `Found ${results.length} memories`,
},
{
type: 'text',
text: JSON.stringify(results, null, 2),
},
],
};
}
case 'memory_insights': {
const insights = await getMemoryStatistics();
if (args?.projectId && typeof args.projectId === 'string') {
const projectInsights = await getProjectInsights(args.projectId);
(insights as any).projectSpecific = projectInsights;
}
return {
content: [
{
type: 'text',
text: 'Memory system insights and patterns',
},
{
type: 'text',
text: JSON.stringify(insights, null, 2),
},
],
};
}
case 'memory_similar': {
await initializeMemory();
const manager = (await import('./memory/index.js')).getMemoryManager();
if (!manager) throw new Error('Memory system not initialized');
if (!args?.analysisId || typeof args.analysisId !== 'string') {
throw new Error('analysisId is required');
}
const analysis = await manager.recall(args.analysisId);
if (!analysis) {
throw new Error(`Analysis ${args.analysisId} not found in memory`);
}
const limitValue = typeof args?.limit === 'number' ? args.limit : 5;
const similar = await getSimilarProjects(analysis.data, limitValue);
return {
content: [
{
type: 'text',
text: `Found ${similar.length} similar projects`,
},
{
type: 'text',
text: JSON.stringify(similar, null, 2),
},
],
};
}
case 'memory_export': {
const format = args?.format === 'json' || args?.format === 'csv' ? args.format : 'json';
const exported = await exportMemories(format);
return {
content: [
{
type: 'text',
text: `Exported memories in ${format} format`,
},
{
type: 'text',
text: exported,
},
],
};
}
case 'memory_cleanup': {
const daysToKeep = typeof args?.daysToKeep === 'number' ? args.daysToKeep : 30;
if (args?.dryRun) {
const stats = await getMemoryStatistics();
const cutoff = new Date(Date.now() - daysToKeep * 24 * 60 * 60 * 1000);
const oldCount = Object.entries((stats as any).statistics?.byMonth || {})
.filter(([month]) => new Date(month + '-01') < cutoff)
.reduce((sum, [_, count]) => sum + (count as number), 0);
return {
content: [
{
type: 'text',
text: `Dry run: Would delete approximately ${oldCount} memories older than ${daysToKeep} days`,
},
],
};
} else {
const deleted = await cleanupOldMemories(daysToKeep);
return {
content: [
{
type: 'text',
text: `Cleaned up ${deleted} old memories`,
},
],
};
}
}
case 'memory_intelligent_analysis': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Intelligent analysis feature is being developed',
projectPath: args?.projectPath,
baseAnalysis: args?.baseAnalysis,
},
null,
2,
),
},
],
};
}
case 'memory_enhanced_recommendation': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Enhanced recommendation feature is being developed',
baseRecommendation: args?.baseRecommendation,
projectFeatures: args?.projectFeatures,
},
null,
2,
),
},
],
};
}
case 'memory_learning_stats': {
const stats = await getMemoryStatistics();
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'active',
learningStats: stats,
message: 'Learning stats from current memory system',
},
null,
2,
),
},
],
};
}
case 'memory_knowledge_graph': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Knowledge graph feature is being developed',
query: args?.query,
},
null,
2,
),
},
],
};
}
case 'memory_contextual_search': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Contextual search feature is being developed',
query: args?.query,
context: args?.context,
},
null,
2,
),
},
],
};
}
case 'memory_agent_network': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Agent network feature is being developed',
action: args?.action,
},
null,
2,
),
},
],
};
}
case 'memory_pruning': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Memory pruning feature is being developed',
dryRun: args?.dryRun,
},
null,
2,
),
},
],
};
}
case 'memory_temporal_analysis': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Temporal analysis feature is being developed',
query: args?.query,
},
null,
2,
),
},
],
};
}
case 'memory_visualization': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Memory visualization feature is being developed',
visualizationType: args?.visualizationType,
},
null,
2,
),
},
],
};
}
case 'memory_export_advanced': {
await initializeMemory();
const manager = (await import('./memory/index.js')).getMemoryManager();
if (!manager) throw new Error('Memory system not initialized');
const result = await manager.export('json');
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'success',
exported: result.length,
data: result,
},
null,
2,
),
},
],
};
}
case 'memory_import_advanced': {
await initializeMemory();
const manager = (await import('./memory/index.js')).getMemoryManager();
if (!manager) throw new Error('Memory system not initialized');
if (!args?.inputPath || typeof args.inputPath !== 'string') {
throw new Error('inputPath is required');
}
const fs = await import('fs/promises');
const data = await fs.readFile(args.inputPath, 'utf-8');
const result = await manager.import(data, 'json');
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'success',
imported: result,
},
null,
2,
),
},
],
};
}
case 'memory_migration': {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'development',
message: 'Migration functionality not yet implemented',
action: args?.action,
},
null,
2,
),
},
],
};
}
case 'memory_optimization_metrics': {
const stats = await getMemoryStatistics();
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
status: 'active',
optimizationMetrics: stats,
message: 'Optimization metrics from current memory system',
},
null,
2,
),
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
content: [
{
type: 'text',
text: `Error executing ${name}: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Show storage information at startup
const storageDir = process.env.DOCUMCP_STORAGE_DIR || `${process.cwd()}/.documcp/memory`;
console.error('DocuMCP server started successfully');
console.error(`Storage location: ${storageDir}`);
}
main().catch((error) => {
console.error('Failed to start DocuMCP server:', error);
process.exit(1);
});