UNPKG

andrew-dataverse-mcp

Version:

A Model Context Protocol server for Microsoft Dataverse with comprehensive CRUD operations

242 lines (241 loc) • 9.61 kB
#!/usr/bin/env node /** * Dataverse MCP Server * * A Model Context Protocol server that provides comprehensive CRUD operations * for Microsoft Dataverse. Supports creating, reading, updating, and deleting * records, as well as querying with OData filters and managing relationships. */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; // Import configuration and services import { initializeConfig } from './config/environment.js'; import { DataverseService } from './services/dataverseService.js'; import { createLogger } from './utils/logger.js'; import { DataverseAuth } from './auth/dataverseAuth.js'; // Import all tools import { createRecordTool, readRecordTool, updateRecordTool, deleteRecordTool, queryRecordsTool, listTablesTool, } from './tools/index.js'; /** * Parse command line arguments with better handling for different shells */ function parseArguments() { const args = {}; // Skip 'node' and script name let i = 2; while (i < process.argv.length) { const arg = process.argv[i]; // Check if this is a flag argument if (arg && arg.startsWith('--')) { const key = arg.slice(2); // Remove '--' prefix // Check if next argument exists and is not a flag if (i + 1 < process.argv.length && !process.argv[i + 1].startsWith('--')) { args[key] = process.argv[i + 1]; i += 2; // Skip both flag and value } else { // Flag without value (like --auth) args[key] = 'true'; i += 1; } } else { // Skip non-flag arguments i += 1; } } return args; } /** * Check if this is an authentication-only run */ function isAuthenticationRun() { return process.argv.includes('--auth'); } /** * Perform standalone authentication for npx users */ async function performStandaloneAuthentication() { try { console.error('\nšŸ” Dataverse MCP Server - Standalone Authentication'); console.error('='.repeat(60)); console.error('This will authenticate and cache your credentials for future use.\n'); const auth = new DataverseAuth(); const token = await auth.getAccessToken(); console.error('\nāœ… Authentication successful!'); console.error('šŸŽ‰ Your credentials have been cached.'); console.error('\nšŸ“‹ Next steps:'); console.error(' 1. Add the server to your MCP client configuration'); console.error(' 2. Use the Dataverse tools - no authentication needed!'); console.error('\nšŸ’” The cached token will be used automatically.'); console.error(' You won\'t need to authenticate again for a while.\n'); } catch (error) { console.error('\nāŒ Authentication failed:', error instanceof Error ? error.message : 'Unknown error'); console.error('\nšŸ”„ You can retry authentication by running the same command again.'); process.exit(1); } } const logger = createLogger('DataverseMCPServer'); /** * Main server initialization and startup */ async function main() { try { // Parse command line arguments const cliArgs = parseArguments(); // Initialize configuration initializeConfig(cliArgs); // Check if this is an authentication-only run if (isAuthenticationRun()) { await performStandaloneAuthentication(); return; } logger.info('Initializing Dataverse MCP Server...'); // Initialize services const dataverseService = new DataverseService(); // Create MCP server const server = new Server({ name: 'dataverse-mcp-server', version: '0.1.0', }, { capabilities: { tools: {}, }, }); // Register tool handlers server.setRequestHandler(ListToolsRequestSchema, async () => { logger.info('Listing available tools'); return { tools: [ createRecordTool, readRecordTool, updateRecordTool, deleteRecordTool, queryRecordsTool, listTablesTool, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; logger.info(`Executing tool: ${name}`); try { switch (name) { case 'dataverse_create_record': const createdId = await dataverseService.createRecord({ table: args?.table || '', data: args?.data || {}, }); return { content: [ { type: 'text', text: `Record created successfully with ID: ${createdId}`, }, ], }; case 'dataverse_read_record': const record = await dataverseService.readRecord({ table: args?.table || '', id: args?.id || '', select: args?.select, expand: args?.expand, }); return { content: [ { type: 'text', text: JSON.stringify(record, null, 2), }, ], }; case 'dataverse_update_record': await dataverseService.updateRecord({ table: args?.table || '', id: args?.id || '', data: args?.data || {}, }); return { content: [ { type: 'text', text: `Record updated successfully`, }, ], }; case 'dataverse_delete_record': await dataverseService.deleteRecord({ table: args?.table || '', id: args?.id || '', }); return { content: [ { type: 'text', text: `Record deleted successfully`, }, ], }; case 'dataverse_query_records': const results = await dataverseService.queryRecords({ table: args?.table || '', select: args?.select, filter: args?.filter, orderby: args?.orderby, top: args?.top, skip: args?.skip, expand: args?.expand, count: args?.count, }); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; case 'dataverse_list_tables': const tables = await dataverseService.listTables(); return { content: [ { type: 'text', text: `Available tables:\n${tables.join('\n')}`, }, ], }; default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { logger.error(`Tool execution failed: ${error}`); throw error; } }); // Start the server const transport = new StdioServerTransport(); await server.connect(transport); logger.info('Dataverse MCP Server is running and ready to accept requests'); } catch (error) { logger.error('Failed to start Dataverse MCP Server:', error); process.exit(1); } } // Handle graceful shutdown process.on('SIGINT', async () => { logger.info('Received SIGINT, shutting down gracefully...'); process.exit(0); }); process.on('SIGTERM', async () => { logger.info('Received SIGTERM, shutting down gracefully...'); process.exit(0); }); // Start the server main().catch((error) => { console.error('Unhandled error:', error); process.exit(1); });