@softeria/ms-365-mcp-server 0.3.5 → 0.4.1

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.
Files changed (49) hide show
  1. package/.github/workflows/build.yml +3 -0
  2. package/.github/workflows/npm-publish.yml +2 -0
  3. package/README.md +1 -1
  4. package/bin/generate-graph-client.mjs +59 -0
  5. package/bin/{download-openapi.mjs → modules/download-openapi.mjs} +10 -20
  6. package/bin/modules/extract-descriptions.mjs +48 -0
  7. package/bin/modules/generate-mcp-tools.mjs +36 -0
  8. package/bin/modules/simplified-openapi.mjs +78 -0
  9. package/dist/auth-tools.js +80 -0
  10. package/dist/auth.js +219 -0
  11. package/dist/cli.js +21 -0
  12. package/dist/endpoints.json +375 -0
  13. package/dist/generated/client.js +14683 -0
  14. package/dist/generated/endpoint-types.js +1 -0
  15. package/dist/generated/hack.js +37 -0
  16. package/dist/graph-client.js +254 -0
  17. package/dist/graph-tools.js +98 -0
  18. package/dist/index.js +39 -0
  19. package/dist/logger.js +33 -0
  20. package/dist/server.js +32 -0
  21. package/{src/version.mjs → dist/version.js} +0 -2
  22. package/package.json +12 -9
  23. package/src/{auth-tools.mjs → auth-tools.ts} +7 -5
  24. package/src/{auth.mjs → auth.ts} +60 -30
  25. package/src/{cli.mjs → cli.ts} +9 -1
  26. package/src/endpoints.json +375 -0
  27. package/src/generated/README.md +51 -0
  28. package/src/generated/client.ts +24916 -0
  29. package/src/generated/endpoint-types.ts +27 -0
  30. package/src/generated/hack.ts +50 -0
  31. package/src/{graph-client.mjs → graph-client.ts} +53 -18
  32. package/src/graph-tools.ts +174 -0
  33. package/{index.mjs → src/index.ts} +6 -6
  34. package/src/{logger.mjs → logger.ts} +1 -1
  35. package/src/{server.mjs → server.ts} +16 -9
  36. package/src/version.ts +9 -0
  37. package/test/{auth-tools.test.js → auth-tools.test.ts} +41 -38
  38. package/test/{cli.test.js → cli.test.ts} +3 -3
  39. package/test/{graph-api.test.js → graph-api.test.ts} +5 -5
  40. package/test/test-hack.ts +17 -0
  41. package/tsconfig.json +16 -0
  42. package/src/dynamic-tools.mjs +0 -442
  43. package/src/openapi-helpers.mjs +0 -187
  44. package/src/param-mapper.mjs +0 -30
  45. package/test/dynamic-tools.test.js +0 -852
  46. package/test/mappings.test.js +0 -29
  47. package/test/mcp-server.test.js +0 -36
  48. package/test/openapi-helpers.test.js +0 -210
  49. package/test/param-mapper.test.js +0 -56
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { validateEndpoints } from '../src/dynamic-tools.mjs';
3
-
4
- /**
5
- * This test file ensures that all the mappings in TARGET_ENDPOINTS actually match
6
- * the endpoints in the OpenAPI spec. It helps catch issues where:
7
- *
8
- * 1. An endpoint in TARGET_ENDPOINTS doesn't exist in the OpenAPI spec
9
- * 2. The method for an endpoint doesn't match what's in the OpenAPI spec
10
- *
11
- * This is a more automated approach than manually running the app and tailing logs.
12
- */
13
-
14
- describe('Mappings Validation', () => {
15
- it('should verify all TARGET_ENDPOINTS exist in the OpenAPI spec', () => {
16
- const missingEndpoints = validateEndpoints();
17
-
18
- if (missingEndpoints.length > 0) {
19
- console.error('The following endpoints are missing from the OpenAPI spec:');
20
- missingEndpoints.forEach((endpoint) => {
21
- console.error(
22
- `- Tool: ${endpoint.toolName}, Path: ${endpoint.pathPattern}, Method: ${endpoint.method}`
23
- );
24
- });
25
- }
26
-
27
- expect(missingEndpoints).toEqual([]);
28
- });
29
- });
@@ -1,36 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { z } from 'zod';
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
-
5
- vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({
6
- McpServer: vi.fn(() => ({
7
- tool: vi.fn(),
8
- connect: vi.fn().mockResolvedValue(undefined),
9
- name: 'TestServer',
10
- version: '0.1.1',
11
- })),
12
- }));
13
- vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
14
-
15
- describe('MCP Server', () => {
16
- let server;
17
-
18
- beforeEach(() => {
19
- vi.clearAllMocks();
20
- server = new McpServer();
21
- });
22
-
23
- it('should be created with proper configuration', () => {
24
- expect(McpServer).toHaveBeenCalled();
25
- expect(server).toBeDefined();
26
- });
27
-
28
- it('should be able to register tools', () => {
29
- server.tool('test-tool', { param: z.string() }, async () => {});
30
- expect(server.tool).toHaveBeenCalledWith(
31
- 'test-tool',
32
- { param: expect.any(Object) },
33
- expect.any(Function)
34
- );
35
- });
36
- });
@@ -1,210 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from 'vitest';
2
- import { z } from 'zod';
3
-
4
- vi.mock('fs', () => {
5
- return {
6
- default: { readFileSync: vi.fn().mockReturnValue('mock yaml content') },
7
- readFileSync: vi.fn().mockReturnValue('mock yaml content'),
8
- };
9
- });
10
-
11
- vi.mock('js-yaml', () => {
12
- const mockSpec = {
13
- paths: {
14
- '/test/path': {
15
- get: {
16
- parameters: [
17
- { name: 'filter', in: 'query', required: false, schema: { type: 'string' } },
18
- ],
19
- },
20
- },
21
- '/test/{param}/path': {
22
- get: {
23
- parameters: [
24
- { name: 'filter', in: 'query', required: false, schema: { type: 'string' } },
25
- ],
26
- },
27
- },
28
- '/excel/test': {
29
- post: {
30
- requestBody: {
31
- content: {
32
- 'application/json': {
33
- schema: { type: 'object' },
34
- },
35
- },
36
- },
37
- },
38
- },
39
- },
40
- };
41
-
42
- return {
43
- default: { load: vi.fn().mockReturnValue(mockSpec) },
44
- load: vi.fn().mockReturnValue(mockSpec),
45
- };
46
- });
47
-
48
- vi.mock('../src/logger.mjs', () => ({
49
- default: {
50
- info: vi.fn(),
51
- warn: vi.fn(),
52
- error: vi.fn(),
53
- },
54
- }));
55
-
56
- // Mock param-mapper module
57
- vi.mock('../src/param-mapper.mjs', () => ({
58
- createFriendlyParamName: (name) => (name.startsWith('$') ? name.substring(1) : name),
59
- registerParamMapping: vi.fn(),
60
- getOriginalParamName: vi.fn(),
61
- }));
62
-
63
- import * as fs from 'fs';
64
- import * as yaml from 'js-yaml';
65
- import { mapToZodType, buildRequestUrl } from '../src/openapi-helpers.mjs';
66
-
67
- function mockProcessParameter(parameter) {
68
- const schema = parameter.schema?.type === 'string' ? z.string() : z.any();
69
- return parameter.required ? schema : schema.optional();
70
- }
71
-
72
- describe('OpenAPI Helpers', () => {
73
- describe('mapToZodType', () => {
74
- it('should map string schema to z.string()', () => {
75
- const schema = { type: 'string' };
76
- const result = mapToZodType(schema);
77
-
78
- expect(result).toBeInstanceOf(z.ZodString);
79
- });
80
-
81
- it('should map number schema to z.number()', () => {
82
- const schema = { type: 'number' };
83
- const result = mapToZodType(schema);
84
-
85
- expect(result).toBeInstanceOf(z.ZodNumber);
86
- });
87
-
88
- it('should map integer schema to z.number()', () => {
89
- const schema = { type: 'integer' };
90
- const result = mapToZodType(schema);
91
-
92
- expect(result).toBeInstanceOf(z.ZodNumber);
93
- });
94
-
95
- it('should map boolean schema to z.boolean()', () => {
96
- const schema = { type: 'boolean' };
97
- const result = mapToZodType(schema);
98
-
99
- expect(result).toBeInstanceOf(z.ZodBoolean);
100
- });
101
-
102
- it('should map array schema to z.array()', () => {
103
- const schema = { type: 'array', items: { type: 'string' } };
104
- const result = mapToZodType(schema);
105
-
106
- expect(result).toBeInstanceOf(z.ZodArray);
107
- });
108
-
109
- it('should map object schema to z.object()', () => {
110
- const schema = {
111
- type: 'object',
112
- properties: {
113
- name: { type: 'string' },
114
- age: { type: 'integer' },
115
- },
116
- };
117
- const result = mapToZodType(schema);
118
-
119
- expect(result).toBeInstanceOf(z.ZodObject);
120
- });
121
-
122
- it('should handle $ref schema', () => {
123
- const schema = { $ref: '#/components/schemas/StringType' };
124
- const result = mapToZodType(schema);
125
-
126
- expect(result).toBeInstanceOf(z.ZodString);
127
- });
128
- });
129
-
130
- describe('buildRequestUrl', () => {
131
- it('should build a URL without parameters', () => {
132
- const baseUrl = '/test/path';
133
- const params = {};
134
- const pathParams = [];
135
- const queryParamDefs = [];
136
-
137
- const result = buildRequestUrl(baseUrl, params, pathParams, queryParamDefs);
138
-
139
- expect(result).toBe('/test/path');
140
- });
141
-
142
- it('should replace path parameters', () => {
143
- const baseUrl = '/test/{id}/path';
144
- const params = { id: '123' };
145
- const pathParams = ['{id}'];
146
- const queryParamDefs = [];
147
-
148
- const result = buildRequestUrl(baseUrl, params, pathParams, queryParamDefs);
149
-
150
- expect(result).toBe('/test/123/path');
151
- });
152
-
153
- it('should add query parameters', () => {
154
- const baseUrl = '/test/path';
155
- const params = { filter: 'test' };
156
- const pathParams = [];
157
- const queryParamDefs = [{ name: 'filter', in: 'query' }];
158
-
159
- const result = buildRequestUrl(baseUrl, params, pathParams, queryParamDefs);
160
-
161
- expect(result).toBe('/test/path?filter=test');
162
- });
163
-
164
- it('should handle Excel range addresses', () => {
165
- const baseUrl = "/workbook/worksheets/{id}/range(address='{address}')";
166
- const params = { id: 'Sheet1', address: 'A1:C10' };
167
- const pathParams = ['{id}'];
168
- const queryParamDefs = [];
169
-
170
- const result = buildRequestUrl(baseUrl, params, pathParams, queryParamDefs);
171
-
172
- expect(result).toBe("/workbook/worksheets/Sheet1/range(address='A1%3AC10')");
173
- });
174
-
175
- it('should handle array parameters', () => {
176
- const baseUrl = '/test/path';
177
- const params = { select: ['name', 'email'] };
178
- const pathParams = [];
179
- const queryParamDefs = [{ name: '$select', in: 'query' }];
180
- const toolName = 'test-tool';
181
-
182
- const result = buildRequestUrl(baseUrl, params, pathParams, queryParamDefs);
183
-
184
- expect(result).toBe('/test/path?$select=name,email');
185
- });
186
-
187
- it('should map friendly parameter names to original names with $ prefix', () => {
188
- const baseUrl = '/test/path';
189
- const params = {
190
- select: 'name,email',
191
- filter: "contains(displayName, 'test')",
192
- orderby: 'displayName',
193
- };
194
- const pathParams = [];
195
- const queryParamDefs = [
196
- { name: '$select', in: 'query' },
197
- { name: '$filter', in: 'query' },
198
- { name: '$orderby', in: 'query' },
199
- ];
200
- const toolName = 'test-tool';
201
-
202
- const result = buildRequestUrl(baseUrl, params, pathParams, queryParamDefs);
203
-
204
- // URL should contain original parameter names with $ prefix
205
- expect(result).toContain('$select=');
206
- expect(result).toContain('$filter=');
207
- expect(result).toContain('$orderby=');
208
- });
209
- });
210
- });
@@ -1,56 +0,0 @@
1
- import { expect, test, describe } from 'vitest';
2
- import {
3
- createFriendlyParamName,
4
- registerParamMapping,
5
- getOriginalParamName,
6
- transformParamsToOriginal
7
- } from '../src/param-mapper.mjs';
8
-
9
- describe('Parameter Mapper', () => {
10
- test('createFriendlyParamName removes $ prefix', () => {
11
- expect(createFriendlyParamName('$orderby')).toBe('orderby');
12
- expect(createFriendlyParamName('$select')).toBe('select');
13
- expect(createFriendlyParamName('$expand')).toBe('expand');
14
- });
15
-
16
- test('createFriendlyParamName leaves regular params unchanged', () => {
17
- expect(createFriendlyParamName('subject')).toBe('subject');
18
- expect(createFriendlyParamName('contentType')).toBe('contentType');
19
- });
20
-
21
- test('registerParamMapping and getOriginalParamName work together', () => {
22
- // Register a mapping
23
- registerParamMapping('list-mail-messages', 'orderby', '$orderby');
24
-
25
- // Retrieve the original name
26
- expect(getOriginalParamName('list-mail-messages', 'orderby')).toBe('$orderby');
27
-
28
- // Unknown mappings return the friendly name
29
- expect(getOriginalParamName('list-mail-messages', 'unknown')).toBe('unknown');
30
- expect(getOriginalParamName('unknown-tool', 'orderby')).toBe('orderby');
31
- });
32
-
33
- test('transformParamsToOriginal converts all params', () => {
34
- // Register mappings
35
- registerParamMapping('list-mail-messages', 'orderby', '$orderby');
36
- registerParamMapping('list-mail-messages', 'select', '$select');
37
- registerParamMapping('list-mail-messages', 'top', '$top');
38
-
39
- // Transform params
40
- const params = {
41
- orderby: 'receivedDateTime desc',
42
- select: 'subject,from,receivedDateTime',
43
- top: 10,
44
- normalParam: 'value'
45
- };
46
-
47
- const transformed = transformParamsToOriginal('list-mail-messages', params);
48
-
49
- expect(transformed).toEqual({
50
- '$orderby': 'receivedDateTime desc',
51
- '$select': 'subject,from,receivedDateTime',
52
- '$top': 10,
53
- 'normalParam': 'value'
54
- });
55
- });
56
- });