mcp-talent-server
Version:
Model Context Protocol server for talent management tools
503 lines • 21 kB
JavaScript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import dotenv from 'dotenv';
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
import { connectToDatabase } from './services/database.js';
import { GalleryZipExportDescription, GalleryZipExporter, galleryZipExportSchema } from './tools/gallery-zip-export.js';
import { StructuredSheetSearchDescription, StructuredSheetSearchTool, structuredSheetSearchSchema } from './tools/structured-sheets-search.js';
import { VectorSearchDescription, VectorSearchTool, vectorSearchSchema } from './tools/vector-search.js';
import { getSchemas, SheetSchemaTracker } from './models/sheet-schema-tracker.model.js';
import { sheetSchemaDescription } from './tools/sheet-schema.js';
import { RosterFileContent, RosterFilesLinksListDescription, RosterFilesListDescription, RosterMainDescription, RosterTool } from './tools/roster-tool.js';
import DocumentModel from './models/document.model.js';
import Sheet, { SheetData } from './models/sheet.model.js';
import { OrignalSheetsColumnsListDescription, OrignalSheetsListDescription, OrignalSheetContentSearchDescription } from './tools/sheet.js';
dotenv.config({ debug: false });
class TalentMCPServer {
server;
galleryZipExporter;
structuredSheetSearch;
vectorSearch;
rosterTool;
constructor() {
this.server = new Server({
name: 'talent-management-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.galleryZipExporter = new GalleryZipExporter();
this.structuredSheetSearch = new StructuredSheetSearchTool();
this.vectorSearch = new VectorSearchTool();
this.rosterTool = new RosterTool();
this.setupToolHandlers();
this.setupErrorHandling();
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'structured_sheets',
description: 'List all sheets',
inputSchema: {
type: "object",
properties: {},
additionalProperties: false
},
},
{
name: 'structured_sheet_schema',
description: 'Get schema for a sheet',
inputSchema: {
type: "object",
properties: {
sheetName: {
type: "string",
description: "Name of the sheet to get schema for"
}
},
required: ["sheetName"]
},
},
{
name: 'gallery_zip_export',
description: GalleryZipExportDescription,
inputSchema: {
type: "object",
properties: {
folderNames: {
type: "array",
items: { type: "string" },
description: "Folder names to search for"
},
imageNames: {
type: "array",
items: { type: "string" },
description: "Image names to search for"
},
foldersSearchQuery: {
type: "string",
description: "MongoDB query object to search folders"
},
imagesSearchQuery: {
type: "string",
description: "MongoDB query object to search images"
},
includeSubfolders: {
type: "boolean",
default: true,
description: "Whether to include images from subfolders"
},
maxImages: {
type: "number",
minimum: 1,
maximum: 100,
default: 50,
description: "Maximum number of images to include in zip"
}
},
required: ["folderNames", "imageNames", "foldersSearchQuery", "imagesSearchQuery"]
},
},
{
name: "structured_sheets_schema",
description: sheetSchemaDescription,
inputSchema: {
type: "object",
properties: {},
additionalProperties: false
},
},
{
name: 'structured_sheets_search',
description: StructuredSheetSearchDescription,
inputSchema: {
type: "object",
properties: {
aggregationPipeline: {
type: "array",
items: { type: "object" },
description: "Pre-generated MongoDB aggregation pipeline to execute directly (optional)"
},
limit: {
type: "number",
default: 10,
description: "Number of results to return"
},
skip: {
type: "number",
default: 0,
description: "Number of results to skip"
}
},
required: ["aggregationPipeline"]
},
},
{
name: 'vector_search',
description: VectorSearchDescription,
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
minLength: 1,
description: "Natural language search query to find relevant content in the knowledge base"
},
limit: {
type: "number",
default: 10,
description: "Number of results to return"
}
},
required: ["query"]
},
},
// {
// name: 'roster_tool',
// description: RosterToolDescription,
// inputSchema: {
// type: "object",
// properties: {
// query: {
// type: "string",
// description: "Natural language search query to find relevant content in the knowledge base"
// },
// limit: {
// type: "number",
// default: 10,
// description: "Number of results to return"
// }
// },
// required: ["query"]
// },
// },
{
name: "roster_main",
description: RosterMainDescription,
inputSchema: {
type: "object",
properties: {},
additionalProperties: false
},
},
{
name: "roster_files_list",
description: RosterFilesListDescription,
inputSchema: {
type: "object",
properties: {},
additionalProperties: false
},
},
{
name: "roster_file_content",
description: RosterFileContent,
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Id of the file to get content for"
}
},
required: ["id"]
},
},
{
name: "roster_files_links_list",
description: RosterFilesLinksListDescription,
inputSchema: {
type: "object",
properties: {},
additionalProperties: false
},
},
{
name: "orignal_sheets_list",
description: OrignalSheetsListDescription,
inputSchema: {
type: "object",
properties: {},
additionalProperties: false
},
},
{
name: "orignal_sheet_columns",
description: OrignalSheetsColumnsListDescription,
inputSchema: {
type: "object",
properties: {
sheetId: {
type: "string",
description: "Id of the sheet to get content for"
}
},
required: ["sheetId"]
},
},
{
name: "orignal_sheet_content_search",
description: OrignalSheetContentSearchDescription,
inputSchema: {
type: "object",
properties: {
aggregationPipeline: {
type: "array",
items: { type: "object" },
description: "Pre-generated MongoDB aggregation pipeline to execute directly (optional)"
},
},
required: ["aggregationPipeline"]
},
}
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'gallery_zip_export':
return await this.handleGalleryZipExport(args);
case 'structured_sheets':
return await this.handleSheets(args);
case 'structured_sheet_schema':
return await this.handleSheetSchema(args);
case 'structured_sheets_schema':
return await this.handleSheetsSchema();
case 'structured_sheets_search':
return await this.handleStructuredSheetsSearch(args);
case 'vector_search':
return await this.handleVectorSearch(args);
// case 'roster_tool':
// return await this.handleRosterTool(args);
case 'roster_main':
return await this.handleRosterMain();
case 'roster_files_list':
return await this.handleRosterFilesList();
case 'roster_file_content':
return await this.handleRosterFileContent(args);
case 'roster_files_links_list':
return await this.handleRosterFilesLinksList();
case 'orignal_sheets_list':
return await this.handleOrignalSheetsList();
case 'orignal_sheet_columns':
return await this.handleOrignalSheetColumns(args);
case 'orignal_sheet_content_search':
return await this.handleOrignalSheetContentSearch(args);
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
});
}
async handleGalleryZipExport(args) {
const validatedArgs = galleryZipExportSchema.parse(args);
const result = await this.galleryZipExporter.exportGallerySearch(validatedArgs);
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: true,
message: `Successfully created zip file with ${result.stats.totalImages} images.`,
downloadUrl: result.downloadUrl,
filename: result.filename,
stats: result.stats,
instructions: 'Click the download URL to get your zip file. The file will include all images organized in their original folder structure, plus an export-metadata.json file with details about the export.',
}, null, 2),
},
],
};
}
async handleSheetSchema(args) {
const result = await SheetSchemaTracker.findOne({ sheetName: args.sheetName });
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleSheets(args) {
const result = await SheetSchemaTracker.find({});
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleSheetsSchema() {
const result = await getSchemas();
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleStructuredSheetsSearch(args) {
const validatedArgs = structuredSheetSearchSchema.parse(args);
const userId = args.userId;
const sessionId = args.sessionId;
const result = await this.structuredSheetSearch.searchInAllSheets(validatedArgs, userId, sessionId);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleVectorSearch(args) {
const validatedArgs = vectorSearchSchema.parse(args);
const result = await this.vectorSearch.search(validatedArgs);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
// private async handleRosterTool(args: any) {
// const validatedArgs = rosterToolSchema.parse(args);
// const result = await this.rosterTool.search(validatedArgs);
// return {
// content: [
// {
// type: 'text',
// text: JSON.stringify(result, null, 2),
// },
// ],
// };
// }
async handleRosterMain() {
const result = await DocumentModel.findOne({ fileName: "All Roster Blast Responses- By Category" }).select("content");
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleRosterFilesList() {
const result = await DocumentModel.find().select("_id fileName");
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleRosterFileContent(args) {
const result = await DocumentModel.findOne({ _id: args.id }).select("content");
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleRosterFilesLinksList() {
const result = await DocumentModel.find().select("_id fileName metadata");
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleOrignalSheetsList() {
const result = await Sheet.find({ deleted: false }).select("name description columns.name");
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleOrignalSheetColumns(args) {
const result = await Sheet.findOne({ _id: args.sheetId }).select("columns");
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
async handleOrignalSheetContentSearch(args) {
const result = await SheetData.aggregate(args.aggregationPipeline);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
setupErrorHandling() {
this.server.onerror = (error) => {
console.error('[MCP Error]', error);
};
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
async start() {
try {
// Connect to database
const mongoUri = process.env.MONGODB_URI || 'mongodb+srv://usama:8YQ4KYmRAyb!m6L@cluster0.yneqevx.mongodb.net/talent_pitch_memory_bank?authSource=admin';
await connectToDatabase(mongoUri);
// Start the server
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Talent MCP Server running on stdio');
}
catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
}
}
// Start the server
export const talentServer = new TalentMCPServer();
talentServer.start().catch((error) => {
console.error('Server startup failed:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map