@dashflow/ms365-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { downloadGraphOpenAPI } from './modules/download-openapi.mjs';
6
+ import { generateMcpTools } from './modules/generate-mcp-tools.mjs';
7
+ import { createAndSaveSimplifiedOpenAPI } from './modules/simplified-openapi.mjs';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const rootDir = path.resolve(__dirname, '..');
12
+ const openapiDir = path.join(rootDir, 'openapi');
13
+ const srcDir = path.join(rootDir, 'src');
14
+
15
+ const openapiFile = path.join(openapiDir, 'openapi.yaml');
16
+ const openapiTrimmedFile = path.join(openapiDir, 'openapi-trimmed.yaml');
17
+ const endpointsFile = path.join(srcDir, 'endpoints.json');
18
+
19
+ const generatedDir = path.join(srcDir, 'generated');
20
+
21
+ const args = process.argv.slice(2);
22
+ const forceDownload = args.includes('--force');
23
+
24
+ async function main() {
25
+ console.log('Microsoft Graph API OpenAPI Processor');
26
+ console.log('------------------------------------');
27
+
28
+ try {
29
+ console.log('\n📥 Step 1: Downloading OpenAPI specification');
30
+ const downloaded = await downloadGraphOpenAPI(
31
+ openapiDir,
32
+ openapiFile,
33
+ undefined,
34
+ forceDownload
35
+ );
36
+
37
+ if (downloaded) {
38
+ console.log('\n✅ OpenAPI specification successfully downloaded');
39
+ } else {
40
+ console.log('\n⏭️ Download skipped (file exists)');
41
+ }
42
+
43
+ console.log('\n🔧 Step 2: Creating simplified OpenAPI specification');
44
+ createAndSaveSimplifiedOpenAPI(endpointsFile, openapiFile, openapiTrimmedFile);
45
+ console.log('✅ Successfully created simplified OpenAPI specification');
46
+
47
+ console.log('\n🚀 Step 3: Generating client code using openapi-zod-client');
48
+ generateMcpTools(null, generatedDir);
49
+ console.log('✅ Successfully generated client code');
50
+ } catch (error) {
51
+ console.error('\n❌ Error processing OpenAPI specification:', error.message);
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ main().catch((error) => {
57
+ console.error('Fatal error:', error);
58
+ process.exit(1);
59
+ });
@@ -0,0 +1,40 @@
1
+ import fs from 'fs';
2
+
3
+ const DEFAULT_OPENAPI_URL =
4
+ 'https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/refs/heads/master/openapi/v1.0/openapi.yaml';
5
+
6
+ export async function downloadGraphOpenAPI(
7
+ targetDir,
8
+ targetFile,
9
+ openapiUrl = DEFAULT_OPENAPI_URL,
10
+ forceDownload = false
11
+ ) {
12
+ if (!fs.existsSync(targetDir)) {
13
+ console.log(`Creating directory: ${targetDir}`);
14
+ fs.mkdirSync(targetDir, { recursive: true });
15
+ }
16
+
17
+ if (fs.existsSync(targetFile) && !forceDownload) {
18
+ console.log(`OpenAPI specification already exists at ${targetFile}`);
19
+ console.log('Use --force to download again');
20
+ return false;
21
+ }
22
+
23
+ console.log(`Downloading OpenAPI specification from ${openapiUrl}`);
24
+
25
+ try {
26
+ const response = await fetch(openapiUrl);
27
+
28
+ if (!response.ok) {
29
+ throw new Error(`Failed to download: ${response.status} ${response.statusText}`);
30
+ }
31
+
32
+ const content = await response.text();
33
+ fs.writeFileSync(targetFile, content);
34
+ console.log(`OpenAPI specification downloaded to ${targetFile}`);
35
+ return true;
36
+ } catch (error) {
37
+ console.error('Error downloading OpenAPI specification:', error.message);
38
+ throw error;
39
+ }
40
+ }
@@ -0,0 +1,48 @@
1
+ import fs from 'fs';
2
+ import yaml from 'js-yaml';
3
+
4
+ export function convertPathToOpenApiFormat(pathPattern) {
5
+ let path = pathPattern.replace(/\{([^}]+)\}/g, (match, param) => {
6
+ const normalizedParam = param.replace(/-/g, '_');
7
+ return `{${normalizedParam}}`;
8
+ });
9
+
10
+ path = path.replace(/\{([^}]+)_id(\d+)\}/g, (match, param, num) => {
11
+ return `{${param}_id_${num}}`;
12
+ });
13
+
14
+ if (!path.startsWith('/')) {
15
+ path = '/' + path;
16
+ }
17
+
18
+ return path;
19
+ }
20
+
21
+ export function extractDescriptions(openapiFile, endpoints) {
22
+ console.log('Extracting descriptions from OpenAPI spec...');
23
+
24
+ const openApiSpec = yaml.load(fs.readFileSync(openapiFile, 'utf8'));
25
+ const descriptions = {};
26
+
27
+ endpoints.forEach((endpoint) => {
28
+ const path = convertPathToOpenApiFormat(endpoint.pathPattern);
29
+ const method = endpoint.method.toLowerCase();
30
+
31
+ if (openApiSpec.paths && openApiSpec.paths[path] && openApiSpec.paths[path][method]) {
32
+ const operation = openApiSpec.paths[path][method];
33
+
34
+ const description =
35
+ operation.description || operation.summary || `Operation for ${endpoint.toolName}`;
36
+
37
+ descriptions[endpoint.toolName] = description;
38
+ console.log(
39
+ `Found description for ${endpoint.toolName}: ${description.substring(0, 50)}${description.length > 50 ? '...' : ''}`
40
+ );
41
+ } else {
42
+ console.warn(`Path ${path} ${method} not found in OpenAPI spec`);
43
+ descriptions[endpoint.toolName] = `Operation for ${endpoint.toolName}`;
44
+ }
45
+ });
46
+
47
+ return descriptions;
48
+ }
@@ -0,0 +1,46 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { execSync } from 'child_process';
4
+
5
+ export function generateMcpTools(openApiSpec, outputDir) {
6
+ try {
7
+ console.log('Generating client code from OpenAPI spec using openapi-zod-client...');
8
+
9
+ if (!fs.existsSync(outputDir)) {
10
+ fs.mkdirSync(outputDir, { recursive: true });
11
+ console.log(`Created directory: ${outputDir}`);
12
+ }
13
+
14
+ const rootDir = path.resolve(outputDir, '../..');
15
+ const openapiDir = path.join(rootDir, 'openapi');
16
+ const openapiTrimmedFile = path.join(openapiDir, 'openapi-trimmed.yaml');
17
+
18
+ const clientFilePath = path.join(outputDir, 'client.ts');
19
+ execSync(
20
+ `npx -y openapi-zod-client "${openapiTrimmedFile}" -o "${clientFilePath}" --with-description --strict-objects --additional-props-default-value=false`,
21
+ {
22
+ stdio: 'inherit',
23
+ }
24
+ );
25
+
26
+ console.log(`Generated client code at: ${clientFilePath}`);
27
+
28
+ let clientCode = fs.readFileSync(clientFilePath, 'utf-8');
29
+ clientCode = clientCode.replace(/'@zodios\/core';/, "'./hack.js';");
30
+
31
+ clientCode = clientCode.replace(
32
+ /const microsoft_graph_attachment = z\s+\.object\({[\s\S]*?}\)\s+\.strict\(\);/,
33
+ (match) => match.replace(/\.strict\(\);/, '.passthrough();')
34
+ );
35
+
36
+ console.log('Stripping unused errors arrays from endpoint definitions...');
37
+ // I didn't make up this crazy regex myself; you know who did. It seems works though.
38
+ clientCode = clientCode.replace(/,?\s*errors:\s*\[[\s\S]*?],?(?=\s*})/g, '');
39
+
40
+ fs.writeFileSync(clientFilePath, clientCode);
41
+
42
+ return true;
43
+ } catch (error) {
44
+ throw new Error(`Error generating client code: ${error.message}`);
45
+ }
46
+ }