@the_cfdude/productboard-mcp
Version:
Model Context Protocol server for Productboard REST API with dynamic tool loading
1,246 lines (1,211 loc) ⢠28.5 kB
text/typescript
/**
* Generate tool manifest from actual MCP server tools
*/
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = join(__dirname, '..');
const OUTPUT_DIR = join(PROJECT_ROOT, 'generated');
const MANIFEST_PATH = join(OUTPUT_DIR, 'manifest.json');
// Ensure output directory exists
if (!existsSync(OUTPUT_DIR)) {
mkdirSync(OUTPUT_DIR, { recursive: true });
}
interface ToolInfo {
category: string;
operation: string;
description: string;
requiredParams: string[];
optionalParams: string[];
implementation: string;
}
interface CategoryInfo {
displayName: string;
description: string;
tools: string[];
}
interface ToolManifest {
version: string;
generated: string;
categories: Record<string, CategoryInfo>;
tools: Record<string, ToolInfo>;
}
/**
* Tools that have static implementations and should NOT be included in manifest
* These tools have their own detailed schemas in their respective modules
*/
const STATIC_IMPLEMENTATION_TOOLS = [
'create_feature',
'update_feature',
'delete_feature',
'get_features',
'get_feature',
'create_component',
'update_component',
'get_components',
'get_component',
'create_product',
'update_product',
'get_products',
'get_product',
'create_note',
'update_note',
'delete_note',
'get_notes',
'get_note',
'create_company',
'update_company',
'delete_company',
'get_companies',
'get_company',
'create_user',
'update_user',
'delete_user',
'get_users',
'get_user',
'create_release',
'update_release',
'delete_release',
'get_releases',
'get_release',
'create_release_group',
'update_release_group',
'delete_release_group',
'get_release_groups',
'get_release_group',
'create_webhook',
'list_webhooks',
'get_webhook',
'delete_webhook',
'create_objective',
'update_objective',
'delete_objective',
'get_objectives',
'get_objective',
'create_initiative',
'update_initiative',
'delete_initiative',
'get_initiatives',
'get_initiative',
'create_key_result',
'update_key_result',
'delete_key_result',
'get_key_results',
'get_key_result',
'get_custom_fields',
'get_custom_field',
'get_custom_fields_values',
'get_custom_field_value',
'set_custom_field_value',
'delete_custom_field_value',
'get_feature_statuses',
];
/**
* Load tools from actual MCP server implementations
*/
async function loadMcpTools(): Promise<{
tools: Record<string, ToolInfo>;
categories: Record<string, CategoryInfo>;
}> {
const tools: Record<string, ToolInfo> = {};
const categories: Record<string, CategoryInfo> = {};
// Helper to register a category
const registerCategory = (
key: string,
displayName: string,
description: string
) => {
if (!categories[key]) {
categories[key] = {
displayName,
description,
tools: [],
};
}
};
// Helper to register a tool
const registerTool = (
name: string,
category: string,
description: string,
requiredParams: string[],
optionalParams: string[]
) => {
// Skip static implementation tools from manifest but add to category
if (STATIC_IMPLEMENTATION_TOOLS.includes(name)) {
console.log(`āļø Skipping ${name} (static implementation)`);
// Still add to category tools array
if (categories[category]) {
categories[category].tools.push(name);
}
return;
}
tools[name] = {
category,
operation: name.toUpperCase(),
description,
requiredParams,
optionalParams,
implementation: 'mcp',
};
if (categories[category]) {
categories[category].tools.push(name);
}
};
// Notes tools
registerCategory('notes', 'Notes', 'Customer feedback and notes management');
registerTool(
'create_note',
'notes',
'Create a new note in Productboard',
['title', 'content'],
[
'ownerEmail',
'userEmail',
'userName',
'userExternalId',
'companyDomain',
'sourceOrigin',
'sourceRecordId',
'displayUrl',
'tags',
]
);
registerTool(
'get_notes',
'notes',
'List all notes with filtering and pagination',
[],
[
'limit',
'startWith',
'detail',
'includeSubData',
'allTags',
'anyTag',
'companyId',
'createdFrom',
'createdTo',
'dateFrom',
'dateTo',
'featureId',
'ownerEmail',
'source',
'term',
'updatedFrom',
'updatedTo',
]
);
registerTool(
'get_note',
'notes',
'Get a specific note by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_note',
'notes',
'Update an existing note',
['id'],
['title', 'content', 'tags']
);
registerTool('delete_note', 'notes', 'Delete a note', ['id'], []);
registerTool(
'bulk_add_note_followers',
'notes',
'Add followers to a note',
['noteId'],
['body']
);
registerTool(
'remove_note_follower',
'notes',
'Remove a follower from a note',
['noteId', 'email'],
[]
);
registerTool('list_tags', 'notes', 'List tags', ['noteId'], []);
registerTool(
'create_note_tag',
'notes',
'Create a tag',
['noteId', 'tagName'],
[]
);
registerTool(
'delete_note_tag',
'notes',
'Remove a tag from a note',
['noteId', 'tagName'],
[]
);
registerTool('list_links', 'notes', 'List links', ['noteId'], []);
registerTool(
'create_link',
'notes',
'Create a link',
['noteId', 'entityId'],
[]
);
registerTool(
'list_feedback_form_configurations',
'notes',
'List all feedback form configurations',
[],
[]
);
registerTool(
'get_feedback_form_configuration',
'notes',
'Retrieve a feedback form configuration',
['id'],
[]
);
registerTool(
'submit_feedback_form',
'notes',
'Submit a feedback form',
[],
['body']
);
// Features tools (includes components and products)
registerCategory(
'features',
'Features',
'Product features, components, and products management'
);
registerTool(
'create_feature',
'features',
'Create a new feature in Productboard',
['name'],
['description', 'status', 'owner', 'parent']
);
registerTool(
'get_features',
'features',
'List all features in Productboard',
[],
[
'limit',
'startWith',
'detail',
'includeSubData',
'archived',
'noteId',
'ownerEmail',
'parentId',
'statusId',
'statusName',
]
);
registerTool(
'get_feature',
'features',
'Get a specific feature by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_feature',
'features',
'Update a feature. Supports both standard fields and custom fields - pass custom field names as additional parameters (e.g., "T-Shirt Sizing": "large").',
['id'],
[
'name',
'description',
'status',
'owner',
'archived',
'timeframe',
'parentId',
'componentId',
'productId',
'...customFields',
]
);
registerTool('delete_feature', 'features', 'Delete a feature', ['id'], []);
registerTool(
'create_component',
'features',
'Create a new component in Productboard',
['name'],
['description', 'parent']
);
registerTool(
'get_components',
'features',
'List all components in Productboard',
[],
['limit', 'startWith', 'detail', 'includeSubData', 'productId']
);
registerTool(
'get_component',
'features',
'Get a specific component by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_component',
'features',
'Update a component',
['id'],
['name', 'description']
);
registerTool(
'get_products',
'features',
'List all products in Productboard',
[],
['limit', 'startWith', 'detail', 'includeSubData']
);
registerTool(
'get_product',
'features',
'Get a specific product by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_product',
'features',
'Update a product',
['id'],
['name', 'description']
);
// Companies tools (includes users)
registerCategory('companies', 'Companies', 'Company and user management');
registerTool(
'create_company',
'companies',
'Create a new company',
['name'],
['description', 'domain', 'externalId']
);
registerTool(
'get_companies',
'companies',
'List all companies',
[],
[
'detail',
'featureId',
'hasNotes',
'includeSubData',
'limit',
'startWith',
'term',
]
);
registerTool(
'get_company',
'companies',
'Retrieve a specific company',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_company',
'companies',
'Update a company',
['id', 'body'],
[]
);
registerTool('delete_company', 'companies', 'Delete a company', ['id'], []);
registerTool(
'create_company_field',
'companies',
'Create a company field',
['body'],
[]
);
registerTool(
'list_company_fields',
'companies',
'Retrieve company fields',
[],
[]
);
registerTool(
'get_company_field',
'companies',
'Retrieve a company field',
['id'],
[]
);
registerTool(
'update_company_field',
'companies',
'Update a company field',
['id', 'body'],
[]
);
registerTool(
'delete_company_field',
'companies',
'Delete a company field',
['id'],
[]
);
registerTool(
'get_company_field_value',
'companies',
'Retrieve company field value',
['companyId', 'companyCustomFieldId'],
[]
);
registerTool(
'set_company_field_value',
'companies',
'Sets company field value',
['companyId', 'companyCustomFieldId', 'body'],
[]
);
registerTool(
'delete_company_field_value',
'companies',
'Delete company field value',
['companyId', 'companyCustomFieldId'],
[]
);
registerTool(
'get_users',
'companies',
'List all users in Productboard',
[],
['detail', 'includeSubData', 'limit', 'startWith']
);
registerTool(
'create_user',
'companies',
'Create a new user in Productboard',
['email'],
['name', 'role', 'companyId', 'externalId']
);
registerTool(
'get_user',
'companies',
'Get a specific user by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_user',
'companies',
'Update an existing user',
['id'],
['name', 'role', 'companyId', 'externalId']
);
registerTool('delete_user', 'companies', 'Delete a user', ['id'], []);
// Webhooks tools
registerCategory('webhooks', 'Webhooks', 'Webhook subscription management');
registerTool(
'post_webhook',
'webhooks',
'Create a new subscription',
['body'],
[]
);
registerTool('get_webhooks', 'webhooks', 'List all subscriptions', [], []);
registerTool(
'get_webhook',
'webhooks',
'Get a specific webhook subscription by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'delete_webhook',
'webhooks',
'Delete a webhook subscription',
['id'],
[]
);
// Objectives tools (includes initiatives and key results)
registerCategory(
'objectives',
'Objectives',
'Strategic objectives, initiatives, and key results management'
);
registerTool(
'get_objectives',
'objectives',
'List all objectives',
[],
['detail', 'includeSubData', 'limit', 'startWith']
);
registerTool(
'create_objective',
'objectives',
'Create a new objective',
['name'],
['description', 'startDate', 'endDate', 'ownerId']
);
registerTool(
'get_objective',
'objectives',
'Get a specific objective by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_objective',
'objectives',
'Update an existing objective',
['id'],
['name', 'description', 'startDate', 'endDate', 'ownerId']
);
registerTool(
'delete_objective',
'objectives',
'Delete an objective',
['id'],
[]
);
registerTool(
'list_links_objective_to_features',
'objectives',
'List features linked to a specific objective',
['id'],
[]
);
registerTool(
'list_links_objective_to_initiatives',
'objectives',
'List initiatives linked to a specific objective',
['id'],
[]
);
registerTool(
'create_objective_to_initiative_link',
'objectives',
'Create a new link between an objective and an initiative',
['id', 'initiativeId'],
[]
);
registerTool(
'delete_objective_to_initiative_link',
'objectives',
'Delete a link between an objective and an initiative',
['id', 'initiativeId'],
[]
);
registerTool(
'create_objective_to_feature_link',
'objectives',
'Create a new link between an objective and a feature',
['id', 'featureId'],
[]
);
registerTool(
'delete_objective_to_feature_link',
'objectives',
'Delete a link between an objective and a feature',
['id', 'featureId'],
[]
);
registerTool(
'get_initiatives',
'objectives',
'List all initiatives',
[],
['detail', 'includeSubData', 'limit', 'startWith']
);
registerTool(
'create_initiative',
'objectives',
'Create a new initiative',
['name'],
['description', 'ownerId', 'status']
);
registerTool(
'get_initiative',
'objectives',
'Get a specific initiative by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_initiative',
'objectives',
'Update an existing initiative',
['id'],
['name', 'description', 'ownerId', 'status']
);
registerTool(
'delete_initiative',
'objectives',
'Delete an initiative',
['id'],
[]
);
registerTool(
'list_links_initiative_to_objectives',
'objectives',
'List objectives linked to a specific initiative',
['id'],
[]
);
registerTool(
'list_links_initiative_to_features',
'objectives',
'List features linked to a specific initiative',
['id'],
[]
);
registerTool(
'create_initiative_to_objective_link',
'objectives',
'Create a new link between an initiative and an objective',
['id', 'objectiveId'],
[]
);
registerTool(
'delete_initiative_to_objective_link',
'objectives',
'Delete a link between an initiative and an objective',
['id', 'objectiveId'],
[]
);
registerTool(
'create_initiative_to_feature_link',
'objectives',
'Create a new link between an initiative and a feature',
['id', 'featureId'],
[]
);
registerTool(
'delete_initiative_to_feature_link',
'objectives',
'Delete a link between an initiative and a feature',
['id', 'featureId'],
[]
);
registerTool(
'get_key_results',
'objectives',
'List all key results',
[],
['detail', 'includeSubData', 'limit', 'startWith']
);
registerTool(
'create_key_result',
'objectives',
'Create a new key result',
['name', 'objectiveId', 'type', 'targetValue'],
['currentValue', 'startValue']
);
registerTool(
'get_key_result',
'objectives',
'Get a specific key result by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_key_result',
'objectives',
'Update an existing key result',
['id'],
['name', 'targetValue', 'currentValue']
);
registerTool(
'delete_key_result',
'objectives',
'Delete a key result',
['id'],
[]
);
// Custom fields tools
registerCategory(
'custom-fields',
'Custom Fields',
'Custom field management for hierarchy entities'
);
registerTool(
'get_custom_fields',
'custom-fields',
'List all custom fields for hierarchy entities',
['type'],
[]
);
registerTool(
'get_custom_fields_values',
'custom-fields',
'List all custom field values',
[],
['customField.id', 'hierarchyEntity.id', 'type']
);
registerTool(
'get_custom_field',
'custom-fields',
'Retrieve a specific custom field',
['id'],
[]
);
registerTool(
'get_custom_field_value',
'custom-fields',
'Retrieve a custom field value for a hierarchy entity',
['customField.id', 'hierarchyEntity.id'],
[]
);
registerTool(
'set_custom_field_value',
'custom-fields',
'Set value of a custom field for a hierarchy entity',
['customField.id', 'hierarchyEntity.id', 'body'],
[]
);
registerTool(
'delete_custom_field_value',
'custom-fields',
'Delete value of a custom field for a hierarchy entity',
['customField.id', 'hierarchyEntity.id'],
[]
);
registerTool(
'get_feature_statuses',
'custom-fields',
'List all feature statuses',
[],
[]
);
// Releases tools
registerCategory(
'releases',
'Releases',
'Release and release group management'
);
registerTool(
'create_release_group',
'releases',
'Create a new release group',
['name'],
['description', 'isDefault']
);
registerTool(
'list_release_groups',
'releases',
'List all release groups',
[],
[]
);
registerTool(
'get_release_group',
'releases',
'Get a specific release group by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_release_group',
'releases',
'Update an existing release group',
['id'],
['name', 'description', 'isDefault']
);
registerTool(
'delete_release_group',
'releases',
'Delete a release group',
['id'],
[]
);
registerTool(
'create_release',
'releases',
'Create a new release',
['name', 'releaseGroupId'],
['description', 'startDate', 'releaseDate', 'state']
);
registerTool(
'list_releases',
'releases',
'List all releases',
[],
['releaseGroup.id']
);
registerTool(
'get_release',
'releases',
'Get a specific release by ID',
['id'],
['detail', 'includeSubData']
);
registerTool(
'update_release',
'releases',
'Update an existing release',
['id'],
['name', 'description', 'startDate', 'releaseDate', 'state']
);
registerTool('delete_release', 'releases', 'Delete a release', ['id'], []);
registerTool(
'list_feature_release_assignments',
'releases',
'List all feature release assignments',
[],
[
'feature.id',
'release.id',
'release.state',
'release.timeframe.endDate.from',
'release.timeframe.endDate.to',
]
);
registerTool(
'get_feature_release_assignment',
'releases',
'Retrieve a feature release assignment',
['release.id', 'feature.id'],
[]
);
registerTool(
'update_feature_release_assignment',
'releases',
'Update a feature release assignment',
['release.id', 'feature.id', 'body'],
[]
);
// Plugin integrations tools
registerCategory(
'plugin-integrations',
'Plugin Integrations',
'Plugin integration management'
);
registerTool(
'post_plugin_integration',
'plugin-integrations',
'Create a plugin integration',
['body'],
[]
);
registerTool(
'get_plugin_integrations',
'plugin-integrations',
'List all plugin integrations',
[],
[]
);
registerTool(
'get_plugin_integration',
'plugin-integrations',
'Retrieve a plugin integration',
['id'],
[]
);
registerTool(
'patch_plugin_integration',
'plugin-integrations',
'Update a plugin integration',
['id', 'body'],
[]
);
registerTool(
'put_plugin_integration',
'plugin-integrations',
'Update a plugin integration',
['id', 'body'],
[]
);
registerTool(
'delete_plugin_integration',
'plugin-integrations',
'Delete a plugin integration',
['id'],
[]
);
registerTool(
'get_plugin_integration_connections',
'plugin-integrations',
'List all plugin integration connections',
['id'],
[]
);
registerTool(
'get_plugin_integration_connection',
'plugin-integrations',
'Retrieve a plugin integration connection',
['id', 'featureId'],
[]
);
registerTool(
'put_plugin_integration_connection',
'plugin-integrations',
'Set a plugin integration connection',
['id', 'featureId', 'body'],
[]
);
registerTool(
'delete_plugin_integration_connection',
'plugin-integrations',
'Delete a plugin integration connection',
['id', 'featureId'],
[]
);
// Jira integrations tools
registerCategory(
'jira-integrations',
'Jira Integrations',
'Jira integration management'
);
registerTool(
'get_jira_integration',
'jira-integrations',
'Retrieve a Jira integration',
['id'],
[]
);
registerTool(
'get_jira_integrations',
'jira-integrations',
'List all Jira integrations',
[],
[]
);
registerTool(
'get_jira_integration_connection',
'jira-integrations',
'Retrieve a Jira integration connection',
['id', 'featureId'],
[]
);
registerTool(
'get_jira_integration_connections',
'jira-integrations',
'List all Jira integration connections',
['id'],
['connection.issueId', 'connection.issueKey']
);
// Search tool (custom)
registerCategory(
'search',
'Universal Search',
'Universal search across all Productboard entities'
);
registerTool(
'search',
'search',
'Universal search across all Productboard entities with flexible filtering and output control',
['entityType'],
[
'filters',
'operators',
'output',
'limit',
'startWith',
'detail',
'includeSubData',
]
);
// Performance tools
registerCategory(
'performance',
'Performance Tools',
'High-performance lightweight tools for status checking, validation, and monitoring'
);
registerTool(
'check_entity_status',
'performance',
'Check status for multiple entities with minimal data transfer. Optimized for quick status overview of large entity sets.',
['entityType', 'ids'],
['fields', 'format', 'useCache', 'instance', 'workspaceId']
);
registerTool(
'validate_entity_existence',
'performance',
'Validate existence of multiple entities efficiently. Returns missing/existing entity lists.',
['entityType', 'ids'],
['returnMissing', 'returnExisting', 'useCache', 'instance', 'workspaceId']
);
registerTool(
'track_batch_progress',
'performance',
'Track progress for batch operations using custom markers (e.g., status:completed, customField).',
['entityType', 'ids', 'progressMarker'],
['includeDetails', 'groupBy', 'useCache', 'instance', 'workspaceId']
);
registerTool(
'get_entity_counts',
'performance',
'Get entity counts without fetching full data. Optimized for dashboard metrics and overview statistics.',
['entityType'],
['filters', 'useCache', 'instance', 'workspaceId']
);
registerTool(
'perform_health_check',
'performance',
'Perform comprehensive system health check including API connectivity, cache status, and memory usage.',
[],
[
'includeDetails',
'includeCacheStats',
'includeMemoryStats',
'instance',
'workspaceId',
]
);
registerTool(
'get_performance_stats',
'performance',
'Get detailed performance statistics including response times, cache hit rates, and percentiles.',
[],
[
'operation',
'includePercentiles',
'clearOldMetrics',
'instance',
'workspaceId',
]
);
registerTool(
'perform_cleanup',
'performance',
'Clear caches and perform system cleanup. Useful for memory management and troubleshooting.',
[],
['clearCache', 'clearMetrics', 'forceGC', 'instance', 'workspaceId']
);
// Bulk operations tools
registerCategory(
'bulk-operations',
'Bulk Operations',
'Efficient bulk update operations with change tracking and diff analysis'
);
registerTool(
'perform_bulk_update',
'bulk-operations',
'Perform batch updates with diff tracking and change analysis. Supports features, notes, companies, users, and objectives.',
['entityType', 'updates'],
[
'batchSize',
'concurrency',
'continueOnError',
'validateBeforeUpdate',
'trackChanges',
'diffFormat',
'includeUnchanged',
'instance',
'workspaceId',
]
);
registerTool(
'compare_entities',
'bulk-operations',
'Compare current entity state with proposed changes without making updates. Shows what would change.',
['entityType', 'comparisons'],
[
'diffFormat',
'highlightSignificant',
'includeUnchanged',
'instance',
'workspaceId',
]
);
registerTool(
'validate_bulk_update',
'bulk-operations',
'Validate bulk update request without executing. Checks for errors and potential issues.',
['entityType', 'updates'],
['checkExistence', 'validateFields', 'instance', 'workspaceId']
);
// Context-aware tools
registerCategory(
'context-aware',
'Context-Aware Features',
'Intelligent response adaptation and user context management with personalization'
);
registerTool(
'set_user_context',
'context-aware',
'Set user context for intelligent response adaptation and personalization.',
['sessionId'],
['userPreferences', 'workspaceContext', 'instanceContext']
);
registerTool(
'get_user_context',
'context-aware',
'Retrieve current user context and preferences.',
['sessionId'],
[]
);
registerTool(
'adapt_response',
'context-aware',
'Adapt a response based on user context and preferences. Provides intelligent formatting and guidance.',
['sessionId', 'query', 'originalResponse'],
['includeGuidance', 'includeSuggestions']
);
registerTool(
'add_adaptation_rule',
'context-aware',
'Add custom adaptation rule for response processing. Allows personalized response handling.',
['name', 'description', 'priority', 'condition', 'adaptation'],
['sessionId']
);
registerTool(
'clear_user_context',
'context-aware',
'Clear all context data for a session. Useful for privacy or starting fresh.',
['sessionId'],
[]
);
registerTool(
'get_context_stats',
'context-aware',
'Get system statistics about context-aware features usage and performance.',
[],
[]
);
return { tools, categories };
}
/**
* Main function to generate manifest
*/
async function main(): Promise<void> {
console.log('š Starting tool manifest generation...');
try {
console.log('š Loading MCP server tools...');
const { tools, categories } = await loadMcpTools();
const manifest: ToolManifest = {
version: '1.0.0',
generated: new Date().toISOString(),
categories,
tools,
};
// Convert arrays to single-line format for CI compatibility
let manifestJson = JSON.stringify(manifest, null, 2);
// Convert multiline arrays to single-line arrays using regex
manifestJson = manifestJson.replace(
/\[\s*\n\s*([^\]]+)\n\s*\]/g,
(match, content) => {
const items = content
.split(',')
.map((item: string) => item.trim())
.filter((item: string) => item.length > 0);
return `[${items.join(', ')}]`;
}
);
writeFileSync(MANIFEST_PATH, manifestJson);
console.log(
`ā
Generated manifest with ${Object.keys(tools).length} tools across ${Object.keys(categories).length} categories`
);
console.log(`š Output: ${MANIFEST_PATH}`);
// Summary
console.log('\nš Category Summary:');
Object.entries(categories).forEach(([key, category]) => {
console.log(
` - ${category.displayName}: ${category.tools.length} tools`
);
});
} catch (error) {
console.error('ā Error generating manifest:', error);
process.exit(1);
}
}
// Run the script
main();