andrew-dataverse-mcp
Version:
A Model Context Protocol server for Microsoft Dataverse with comprehensive CRUD operations
242 lines (241 loc) ⢠9.61 kB
JavaScript
/**
* 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);
});