@frustrated/ms-graph-mcp 0.1.8 → 0.1.10

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.
package/src/config.ts CHANGED
@@ -1,48 +1,52 @@
1
- import { promises as fs } from "node:fs";
2
- import * as path from "node:path";
3
- import * as os from "node:os";
4
-
5
- const CONFIG_DIR = path.join(os.homedir(), ".ms-graph-mcp");
6
- const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
7
-
8
- interface AppConfig {
9
- clientId: string;
10
- tenantId: string;
11
- enabledTools: string[]; // List of tool names that are enabled
12
- }
13
-
14
- let appConfig: AppConfig = {
15
- clientId: "0a74e52a-4d5b-4005-8dad-6b7cf45ec5fe", // Default centralized client ID
16
- tenantId: "common",
17
- enabledTools: [],
18
- };
19
-
20
- export async function loadConfig(): Promise<AppConfig> {
21
- try {
22
- const configContent = await fs.readFile(CONFIG_FILE, "utf-8");
23
- appConfig = { ...appConfig, ...JSON.parse(configContent) };
24
- } catch (error) {
25
- // If file doesn't exist or is invalid, use default config
26
- console.warn(
27
- "No existing config found or config file is invalid. Using default configuration.",
28
- );
29
- }
30
- return appConfig;
31
- }
32
-
33
- export async function saveConfig(newConfig: Partial<AppConfig>): Promise<void> {
34
- appConfig = { ...appConfig, ...newConfig };
35
- await fs.mkdir(CONFIG_DIR, { recursive: true });
36
- await fs.writeFile(CONFIG_FILE, JSON.stringify(appConfig, null, 2));
37
- }
38
-
39
- export function getConfig(): AppConfig {
40
- return appConfig;
41
- }
42
-
43
- export function isToolEnabled(toolName: string): boolean {
44
- return appConfig.enabledTools.includes(toolName);
45
- }
46
-
47
- // Initialize config on module load
48
- loadConfig();
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), ".ms-graph-mcp");
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
7
+
8
+ interface AppConfig {
9
+ clientId: string;
10
+ tenantId: string;
11
+ enabledTools: string[]; // List of tool names that are enabled
12
+ }
13
+
14
+ let appConfig: AppConfig = {
15
+ clientId: "0a74e52a-4d5b-4005-8dad-6b7cf45ec5fe", // Default centralized client ID
16
+ tenantId: "common",
17
+ enabledTools: [],
18
+ };
19
+
20
+ export async function loadConfig(): Promise<AppConfig> {
21
+ try {
22
+ const configContent = await fs.readFile(CONFIG_FILE, "utf-8");
23
+ appConfig = { ...appConfig, ...JSON.parse(configContent) };
24
+ } catch (error) {
25
+ // If file doesn't exist or is invalid, use default config
26
+ console.warn(
27
+ "No existing config found or config file is invalid. Using default configuration.",
28
+ );
29
+ }
30
+ return appConfig;
31
+ }
32
+
33
+ export async function saveConfig(newConfig: Partial<AppConfig>): Promise<void> {
34
+ appConfig = { ...appConfig, ...newConfig };
35
+ await fs.mkdir(CONFIG_DIR, { recursive: true });
36
+ await fs.writeFile(CONFIG_FILE, JSON.stringify(appConfig, null, 2));
37
+ }
38
+
39
+ export function getConfig(): AppConfig {
40
+ return appConfig;
41
+ }
42
+
43
+ export function isToolEnabled(toolName: string): boolean {
44
+ // Empty enabledTools means all tools are enabled (no explicit restriction)
45
+ if (appConfig.enabledTools.length === 0) {
46
+ return true;
47
+ }
48
+ return appConfig.enabledTools.includes(toolName);
49
+ }
50
+
51
+ // Initialize config on module load
52
+ loadConfig();
package/src/index.ts CHANGED
@@ -1,80 +1,77 @@
1
- #!/usr/bin/env node
2
- import { Command } from "@commander-js/extra-typings";
3
- import { initAuth, revokeAuth, getAuthStatus } from "./auth.ts";
4
- import { getConfig, saveConfig } from "./config.ts";
5
- import { startMcpServer } from "./mcp-interface.ts";
6
-
7
- const program = new Command();
8
-
9
- program
10
- .name("ms-graph-mcp")
11
- .description("CLI for JSR-based Microsoft Graph MCP package")
12
- .version("0.1.0");
13
-
14
- program
15
- .command("init")
16
- .description("Initialize authentication with Microsoft Graph")
17
- .action(async () => {
18
- try {
19
- await initAuth();
20
- console.log("Authentication process completed.");
21
- } catch (error) {
22
- console.error("Authentication failed:", error instanceof Error ? error.message : String(error));
23
- process.exit(1);
24
- }
25
- });
26
-
27
- program
28
- .command("revoke")
29
- .description("Revoke Microsoft Graph authentication and clear tokens")
30
- .action(async () => {
31
- try {
32
- await revokeAuth();
33
- console.log("Authentication revoked successfully.");
34
- } catch (error) {
35
- console.error("Failed to revoke authentication:", error instanceof Error ? error.message : String(error));
36
- process.exit(1);
37
- }
38
- });
39
-
40
- program
41
- .command("permissions")
42
- .description(
43
- "Display current authentication status and configured permissions",
44
- )
45
- .action(async () => {
46
- try {
47
- const authStatus = await getAuthStatus();
48
- const config = getConfig();
49
- console.log("--- Microsoft Graph MCP Status ---");
50
- console.log(`Client ID: ${authStatus.clientId}`);
51
- console.log(`Tenant ID: ${authStatus.tenantId}`);
52
- console.log(
53
- `Authenticated: ${authStatus.isAuthenticated ? "Yes" : "No"}`,
54
- );
55
- console.log("\nEnabled Tools:");
56
- if (config.enabledTools.length > 0) {
57
- config.enabledTools.forEach((tool) => console.log(`- ${tool}`));
58
- } else {
59
- console.log(
60
- "No specific tools are explicitly enabled in config. All available tools will be used.",
61
- );
62
- }
63
- console.log("----------------------------------");
64
- } catch (error) {
65
- console.error("Failed to retrieve status:", error instanceof Error ? error.message : String(error));
66
- process.exit(1);
67
- }
68
- });
69
-
70
- program
71
- .command("run")
72
- .description("Start the MCP server to listen for commands via stdin/stdout")
73
- .action(async () => {
74
- console.log("MCP server started. Listening for commands on stdin...");
75
- // Placeholder for MCP interface logic
76
- // This will be implemented in src/mcp-interface.ts and called here
77
- startMcpServer();
78
- });
79
-
80
- program.parse(process.argv);
1
+ #!/usr/bin/env node
2
+ import { Command } from "@commander-js/extra-typings";
3
+ import { initAuth, revokeAuth, getAuthStatus } from "./auth.ts";
4
+ import { getConfig, saveConfig } from "./config.ts";
5
+ import { startMcpServer } from "./mcp-interface.ts";
6
+
7
+ const program = new Command();
8
+
9
+ program
10
+ .name("ms-graph-mcp")
11
+ .description("CLI for JSR-based Microsoft Graph MCP package")
12
+ .version("0.1.0");
13
+
14
+ program
15
+ .command("init")
16
+ .description("Initialize authentication with Microsoft Graph")
17
+ .action(async () => {
18
+ try {
19
+ await initAuth();
20
+ console.log("Authentication process completed.");
21
+ } catch (error) {
22
+ console.error("Authentication failed:", error instanceof Error ? error.message : String(error));
23
+ process.exit(1);
24
+ }
25
+ });
26
+
27
+ program
28
+ .command("revoke")
29
+ .description("Revoke Microsoft Graph authentication and clear tokens")
30
+ .action(async () => {
31
+ try {
32
+ await revokeAuth();
33
+ console.log("Authentication revoked successfully.");
34
+ } catch (error) {
35
+ console.error("Failed to revoke authentication:", error instanceof Error ? error.message : String(error));
36
+ process.exit(1);
37
+ }
38
+ });
39
+
40
+ program
41
+ .command("permissions")
42
+ .description(
43
+ "Display current authentication status and configured permissions",
44
+ )
45
+ .action(async () => {
46
+ try {
47
+ const authStatus = await getAuthStatus();
48
+ const config = getConfig();
49
+ console.log("--- Microsoft Graph MCP Status ---");
50
+ console.log(`Client ID: ${authStatus.clientId}`);
51
+ console.log(`Tenant ID: ${authStatus.tenantId}`);
52
+ console.log(
53
+ `Authenticated: ${authStatus.isAuthenticated ? "Yes" : "No"}`,
54
+ );
55
+ console.log("\nEnabled Tools:");
56
+ if (config.enabledTools.length > 0) {
57
+ config.enabledTools.forEach((tool) => console.log(`- ${tool}`));
58
+ } else {
59
+ console.log(
60
+ "No specific tools are explicitly enabled in config. All available tools will be used.",
61
+ );
62
+ }
63
+ console.log("----------------------------------");
64
+ } catch (error) {
65
+ console.error("Failed to retrieve status:", error instanceof Error ? error.message : String(error));
66
+ process.exit(1);
67
+ }
68
+ });
69
+
70
+ program
71
+ .command("run")
72
+ .description("Start the MCP server to listen for commands via stdin/stdout")
73
+ .action(async () => {
74
+ await startMcpServer();
75
+ });
76
+
77
+ program.parse(process.argv);
@@ -1,122 +1,72 @@
1
- import { getAccessToken } from './auth.ts';
2
- import { getConfig, isToolEnabled } from './config.ts';
3
- import { error, log } from './utils.ts';
4
- import { Client } from '@microsoft/microsoft-graph-client';
5
-
6
- // Define MCP message interfaces
7
- interface McpRequest {
8
- type: 'request';
9
- id: string;
10
- tool: string;
11
- input: any;
12
- }
13
-
14
- interface McpResponse {
15
- type: 'response';
16
- id: string;
17
- status: 'success' | 'error';
18
- output?: any;
19
- error?: { code: string; message: string };
20
- }
21
-
22
- // Placeholder for registered tools
23
- import { tools as registeredTools } from './tools/index.ts';
24
-
25
-
26
- async function processMcpRequest(request: McpRequest): Promise<McpResponse> {
27
- const config = getConfig();
28
-
29
- if (!isToolEnabled(request.tool)) {
30
- return {
31
- type: 'response',
32
- id: request.id,
33
- status: 'error',
34
- error: { code: 'TOOL_DISABLED', message: `Tool '${request.tool}' is disabled.` },
35
- };
36
- }
37
-
38
- const toolHandler = (registeredTools as Record<string, ((graphClient: Client, input: any) => Promise<any>) | undefined>)[request.tool];
39
- if (!toolHandler) {
40
- return {
41
- type: 'response',
42
- id: request.id,
43
- status: 'error',
44
- error: { code: 'TOOL_NOT_FOUND', message: `Tool '${request.tool}' not found.` },
45
- };
46
- }
47
-
48
- try {
49
- const accessToken = await getAccessToken();
50
- const graphClient = Client.init({
51
- authProvider: (done) => {
52
- done(null, accessToken);
53
- },
54
- });
55
-
56
- const output = await toolHandler(graphClient, request.input);
57
- return {
58
- type: 'response',
59
- id: request.id,
60
- status: 'success',
61
- output,
62
- };
63
- } catch (err: any) {
64
- error(`Error executing tool '${request.tool}':`, err);
65
- return {
66
- type: 'response',
67
- id: request.id,
68
- status: 'error',
69
- error: { code: err.code || 'TOOL_EXECUTION_ERROR', message: err.message || 'An unknown error occurred.' },
70
- };
71
- }
72
- }
73
-
74
- export function startMcpServer() {
75
- log('MCP server started. Listening for commands on stdin...');
76
-
77
- process.stdin.setEncoding('utf8');
78
-
79
- let buffer = '';
80
- process.stdin.on('data', async (chunk) => {
81
- buffer += chunk;
82
- let newlineIndex;
83
- while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
84
- const line = buffer.substring(0, newlineIndex).trim();
85
- buffer = buffer.substring(newlineIndex + 1);
86
-
87
- if (line) {
88
- try {
89
- const request: McpRequest = JSON.parse(line);
90
- if (request.type === 'request' && request.id && request.tool) {
91
- const response = await processMcpRequest(request);
92
- process.stdout.write(JSON.stringify(response) + '\n');
93
- } else {
94
- error('Invalid MCP request format:', new Error(line));
95
- process.stdout.write(JSON.stringify({
96
- type: 'response',
97
- id: request.id || 'unknown',
98
- status: 'error',
99
- error: { code: 'INVALID_REQUEST', message: 'Invalid MCP request format.' },
100
- }) + '\n');
101
- }
102
- } catch (parseError: any) {
103
- error('Failed to parse stdin input as JSON:', parseError);
104
- process.stdout.write(JSON.stringify({
105
- type: 'response',
106
- id: 'unknown',
107
- status: 'error',
108
- error: { code: 'JSON_PARSE_ERROR', message: 'Invalid JSON input.' },
109
- }) + '\n');
110
- }
111
- }
112
- }
113
- });
114
-
115
- process.stdin.on('end', () => {
116
- log('Stdin closed. MCP server shutting down.');
117
- });
118
-
119
- process.stdin.on('error', (err) => {
120
- error('Stdin error:', err);
121
- });
122
- }
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { z } from 'zod';
4
+ import { getAccessToken } from './auth.ts';
5
+ import { isToolEnabled } from './config.ts';
6
+ import { log } from './utils.ts';
7
+ import { Client } from '@microsoft/microsoft-graph-client';
8
+ import * as mail from './tools/mail.ts';
9
+ import * as calendar from './tools/calendar.ts';
10
+
11
+ function buildGraphClient(accessToken: string): Client {
12
+ return Client.init({
13
+ authProvider: (done) => done(null, accessToken),
14
+ });
15
+ }
16
+
17
+ export async function startMcpServer(): Promise<void> {
18
+ const server = new McpServer({
19
+ name: 'ms-graph-mcp',
20
+ version: '0.1.10',
21
+ });
22
+
23
+ if (isToolEnabled('mail.list_messages')) {
24
+ server.tool(
25
+ 'mail.list_messages',
26
+ 'List email messages from the signed-in user\'s mailbox',
27
+ {
28
+ folderId: z.string().optional().describe('Mail folder ID (defaults to Inbox)'),
29
+ top: z.number().int().min(1).optional().describe('Maximum number of messages to return'),
30
+ filter: z.string().optional().describe('OData $filter expression'),
31
+ },
32
+ async ({ folderId, top, filter }) => {
33
+ const token = await getAccessToken();
34
+ const result = await mail.listMessages(buildGraphClient(token), { folderId, top, filter });
35
+ return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
36
+ },
37
+ );
38
+ }
39
+
40
+ if (isToolEnabled('calendar.create_event')) {
41
+ server.tool(
42
+ 'calendar.create_event',
43
+ 'Create a new event in the signed-in user\'s calendar',
44
+ {
45
+ subject: z.string().describe('Event title'),
46
+ start: z.object({
47
+ dateTime: z.string().describe('ISO 8601 date-time string'),
48
+ timeZone: z.string().describe('IANA timezone name, e.g. "America/New_York"'),
49
+ }),
50
+ end: z.object({
51
+ dateTime: z.string().describe('ISO 8601 date-time string'),
52
+ timeZone: z.string().describe('IANA timezone name'),
53
+ }),
54
+ content: z.string().optional().describe('HTML body of the event'),
55
+ attendees: z.array(z.object({
56
+ emailAddress: z.string().email(),
57
+ type: z.enum(['required', 'optional']),
58
+ })).optional().describe('List of attendees'),
59
+ location: z.string().optional().describe('Event location display name'),
60
+ },
61
+ async (input) => {
62
+ const token = await getAccessToken();
63
+ const result = await calendar.createEvent(buildGraphClient(token), input);
64
+ return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
65
+ },
66
+ );
67
+ }
68
+
69
+ const transport = new StdioServerTransport();
70
+ await server.connect(transport);
71
+ log('MCP server started (JSON-RPC 2.0 over stdio). Listening for commands...');
72
+ }
@@ -1,41 +1,41 @@
1
- import { Client } from '@microsoft/microsoft-graph-client';
2
- import { log, error } from '../utils.ts';
3
-
4
- export async function createEvent(graphClient: Client, input: {
5
- subject: string;
6
- start: { dateTime: string; timeZone: string };
7
- end: { dateTime: string; timeZone: string };
8
- content?: string;
9
- attendees?: Array<{ emailAddress: string; type: 'required' | 'optional' }>;
10
- location?: string;
11
- }): Promise<any> {
12
- try {
13
- const event = {
14
- subject: input.subject,
15
- start: input.start,
16
- end: input.end,
17
- body: input.content ? { contentType: 'HTML', content: input.content } : undefined,
18
- attendees: input.attendees?.map(att => ({
19
- emailAddress: { address: att.emailAddress },
20
- type: att.type,
21
- })),
22
- location: input.location ? { displayName: input.location } : undefined,
23
- };
24
-
25
- const response = await graphClient.api('/me/events').post(event);
26
- log(`Created calendar event: ${response.subject} (ID: ${response.id})`);
27
- return {
28
- id: response.id,
29
- webLink: response.webLink,
30
- status: 'created',
31
- };
32
- } catch (err: any) {
33
- error('Error creating calendar event:', err);
34
- return {
35
- id: null,
36
- webLink: null,
37
- status: 'failed',
38
- errorMessage: err.message,
39
- };
40
- }
41
- }
1
+ import { Client } from '@microsoft/microsoft-graph-client';
2
+ import { log, error } from '../utils.ts';
3
+
4
+ export async function createEvent(graphClient: Client, input: {
5
+ subject: string;
6
+ start: { dateTime: string; timeZone: string };
7
+ end: { dateTime: string; timeZone: string };
8
+ content?: string;
9
+ attendees?: Array<{ emailAddress: string; type: 'required' | 'optional' }>;
10
+ location?: string;
11
+ }): Promise<any> {
12
+ try {
13
+ const event = {
14
+ subject: input.subject,
15
+ start: input.start,
16
+ end: input.end,
17
+ body: input.content ? { contentType: 'HTML', content: input.content } : undefined,
18
+ attendees: input.attendees?.map(att => ({
19
+ emailAddress: { address: att.emailAddress },
20
+ type: att.type,
21
+ })),
22
+ location: input.location ? { displayName: input.location } : undefined,
23
+ };
24
+
25
+ const response = await graphClient.api('/me/events').post(event);
26
+ log(`Created calendar event: ${response.subject} (ID: ${response.id})`);
27
+ return {
28
+ id: response.id,
29
+ webLink: response.webLink,
30
+ status: 'created',
31
+ };
32
+ } catch (err: any) {
33
+ error('Error creating calendar event:', err);
34
+ return {
35
+ id: null,
36
+ webLink: null,
37
+ status: 'failed',
38
+ errorMessage: err.message,
39
+ };
40
+ }
41
+ }
@@ -1,9 +1,9 @@
1
- import * as mail from './mail.ts';
2
- import * as calendar from './calendar.ts';
3
- // import * as onedrive from './onedrive'; // Placeholder for future tools
4
-
5
- export const tools = {
6
- 'mail.list_messages': mail.listMessages,
7
- 'calendar.create_event': calendar.createEvent,
8
- // 'onedrive.list_files': onedrive.listFiles,
9
- };
1
+ import * as mail from './mail.ts';
2
+ import * as calendar from './calendar.ts';
3
+ // import * as onedrive from './onedrive'; // Placeholder for future tools
4
+
5
+ export const tools = {
6
+ 'mail.list_messages': mail.listMessages,
7
+ 'calendar.create_event': calendar.createEvent,
8
+ // 'onedrive.list_files': onedrive.listFiles,
9
+ };
package/src/tools/mail.ts CHANGED
@@ -1,34 +1,34 @@
1
- import { Client } from '@microsoft/microsoft-graph-client';
2
- import { log, error } from '../utils.ts';
3
-
4
- export async function listMessages(graphClient: Client, input: { folderId?: string; top?: number; filter?: string }): Promise<any> {
5
- try {
6
- let request = graphClient.api(input.folderId ? `/me/mailFolders/${input.folderId}/messages` :
7
- '/me/messages');
8
-
9
- if (input.top) {
10
- request = request.top(input.top);
11
- }
12
- if (input.filter) {
13
- request = request.filter(input.filter);
14
- }
15
-
16
- const response = await request.select('id,subject,from,receivedDateTime,isRead,bodyPreview,webLink').get();
17
- log(`Listed ${response.value.length} messages.`);
18
- return {
19
- messages: response.value.map((msg: any) => ({
20
- id: msg.id,
21
- subject: msg.subject,
22
- from: msg.from,
23
- receivedDateTime: msg.receivedDateTime,
24
- isRead: msg.isRead,
25
- bodyPreview: msg.bodyPreview,
26
- webLink: msg.webLink,
27
- })),
28
- nextLink: response['@odata.nextLink'],
29
- };
30
- } catch (err: any) {
31
- error('Error listing messages:', err);
32
- throw err;
33
- }
34
- }
1
+ import { Client } from '@microsoft/microsoft-graph-client';
2
+ import { log, error } from '../utils.ts';
3
+
4
+ export async function listMessages(graphClient: Client, input: { folderId?: string; top?: number; filter?: string }): Promise<any> {
5
+ try {
6
+ let request = graphClient.api(input.folderId ? `/me/mailFolders/${input.folderId}/messages` :
7
+ '/me/messages');
8
+
9
+ if (input.top) {
10
+ request = request.top(input.top);
11
+ }
12
+ if (input.filter) {
13
+ request = request.filter(input.filter);
14
+ }
15
+
16
+ const response = await request.select('id,subject,from,receivedDateTime,isRead,bodyPreview,webLink').get();
17
+ log(`Listed ${response.value.length} messages.`);
18
+ return {
19
+ messages: response.value.map((msg: any) => ({
20
+ id: msg.id,
21
+ subject: msg.subject,
22
+ from: msg.from,
23
+ receivedDateTime: msg.receivedDateTime,
24
+ isRead: msg.isRead,
25
+ bodyPreview: msg.bodyPreview,
26
+ webLink: msg.webLink,
27
+ })),
28
+ nextLink: response['@odata.nextLink'],
29
+ };
30
+ } catch (err: any) {
31
+ error('Error listing messages:', err);
32
+ throw err;
33
+ }
34
+ }