@frustrated/ms-graph-mcp 0.1.10 → 0.1.11

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/README.md CHANGED
@@ -20,6 +20,7 @@ A TypeScript MCP (Model Context Protocol) package for personal Microsoft Graph a
20
20
  - **Secure Token Storage:** Stores the MSAL token cache at `~/.config/ms-graph-mcp/msal_cache.json` with restricted file permissions (`0600`).
21
21
  - **User Control:** Provides CLI commands for permission management and revocation.
22
22
  - **MCP Standard:** Adheres to the Model Context Protocol for broad agent compatibility.
23
+ - **OneDrive Support:** Lists, inspects, searches, and creates OneDrive files and folders.
23
24
 
24
25
  ## Usage with Manus Agents
25
26
 
@@ -54,6 +55,7 @@ The Microsoft Graph MCP CLI exposes the following tools:
54
55
 
55
56
  - **`mail`**: Manage email communications (e.g., list messages).
56
57
  - **`calendar`**: Organize calendar events (e.g., create events).
58
+ - **`onedrive`**: Work with OneDrive files and folders.
57
59
 
58
60
  For detailed information on specific tools and their functionalities, refer to the [Tools Documentation](./docs/tools/README.md).
59
61
 
@@ -78,6 +80,7 @@ bunx --bun @frustrated/ms-graph-mcp revoke
78
80
  - [Ideation Document](./docs/ms_graph_mcp_ideation.md) - Conceptual design and motivation.
79
81
  - [Technical Specification](./docs/ms_graph_mcp_spec.md) - Detailed implementation guide.
80
82
  - [Tools Documentation](./docs/tools/README.md) - Comprehensive guide to all available MCP tools.
83
+ - [MCP Smoke Test](./docs/mcp-smoke-test.md) - How to test the server directly over stdio with the MCP SDK.
81
84
 
82
85
  ## Security Considerations
83
86
 
@@ -0,0 +1,89 @@
1
+ # MCP smoke testing
2
+
3
+ This guide shows how to test `ms-graph-mcp` as a raw MCP server over `stdio`, without wiring it into another AI app first.
4
+
5
+ The goal is to verify three things:
6
+
7
+ 1. The process starts cleanly.
8
+ 2. The MCP handshake succeeds.
9
+ 3. `tools/list` and at least one `tools/call` work end to end.
10
+
11
+ ## What to expect
12
+
13
+ When the server runs in MCP mode, `stdout` must remain reserved for protocol messages only. Human-readable logs should go to `stderr`.
14
+
15
+ If you see plain text on `stdout`, the MCP framing is broken.
16
+
17
+ ## Prerequisites
18
+
19
+ 1. Authenticate once:
20
+
21
+ ```bash
22
+ bun run src/index.ts init
23
+ ```
24
+
25
+ 2. Confirm the server can start:
26
+
27
+ ```bash
28
+ bun run src/index.ts run
29
+ ```
30
+
31
+ ## Manual smoke test with the MCP SDK
32
+
33
+ The easiest direct test is to use the official MCP SDK from a short Bun script.
34
+
35
+ Create a temporary file such as `scripts/mcp-smoke-test.ts` with the following contents:
36
+
37
+ ```ts
38
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
39
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
40
+
41
+ const transport = new StdioClientTransport({
42
+ command: 'bun',
43
+ args: ['run', 'src/index.ts', 'run'],
44
+ });
45
+
46
+ const client = new Client(
47
+ {
48
+ name: 'mcp-smoke-test',
49
+ version: '1.0.0',
50
+ },
51
+ {
52
+ capabilities: {},
53
+ },
54
+ );
55
+
56
+ await client.connect(transport);
57
+
58
+ const tools = await client.listTools();
59
+ console.log(JSON.stringify(tools, null, 2));
60
+
61
+ // Pick one tool that is enabled in your config.
62
+ // Example: mail.list_messages
63
+ const result = await client.callTool({
64
+ name: 'mail.list_messages',
65
+ arguments: { top: 1 },
66
+ });
67
+
68
+ console.log(JSON.stringify(result, null, 2));
69
+ await client.close();
70
+ ```
71
+
72
+ Run it with:
73
+
74
+ ```bash
75
+ bun run scripts/mcp-smoke-test.ts
76
+ ```
77
+
78
+ ## What to verify
79
+
80
+ - `listTools()` returns the registered tools you expect.
81
+ - `callTool()` returns a successful result object.
82
+ - The server does not print protocol text to `stderr` or `stdout` outside the MCP response stream.
83
+ - A bad input should fail with a clear validation or Graph error instead of an opaque transport error.
84
+
85
+ ## Quick debug checks
86
+
87
+ - If `listTools()` is empty, confirm tool gating in `~/.ms-graph-mcp/config.json`.
88
+ - If a tool call returns `401`, re-run `bun run src/index.ts init`.
89
+ - If a tool call returns `400`, inspect the request payload for invalid dates, missing required fields, or malformed OneDrive paths.
@@ -6,7 +6,7 @@ This document provides an overview of the top-level tools available in the Micro
6
6
 
7
7
  * **[Mail](./mail.md)**: Interact with email messages, folders, and other mail-related functionalities.
8
8
  * **[Calendar](./calendar.md)**: Manage calendar events, schedules, and attendees.
9
- * **OneDrive (Coming Soon)**: Interact with files and folders in OneDrive.
9
+ * **[OneDrive](./onedrive.md)**: List, inspect, search, and create files and folders in OneDrive.
10
10
 
11
11
  ## How to Discover Sub-Tools
12
12
 
@@ -0,0 +1,44 @@
1
+ # OneDrive Tools
2
+
3
+ This document details the sub-tools available under the `onedrive` top-level tool.
4
+
5
+ ## `onedrive.list_items`
6
+
7
+ Lists files and folders from OneDrive root or a specific folder.
8
+
9
+ ### Parameters
10
+
11
+ * `folderPath` (string, optional): A folder path relative to OneDrive root, such as `/Projects`.
12
+ * `itemId` (string, optional): A drive item ID whose children should be listed.
13
+ * `top` (number, optional): Maximum number of items to return.
14
+ * `select` (string, optional): Optional OData `$select` clause.
15
+
16
+ ## `onedrive.get_item`
17
+
18
+ Gets metadata for a OneDrive file or folder.
19
+
20
+ ### Parameters
21
+
22
+ * `path` (string, optional): Item path relative to OneDrive root, such as `/Projects/report.docx`.
23
+ * `itemId` (string, optional): Drive item ID.
24
+ * `select` (string, optional): Optional OData `$select` clause.
25
+
26
+ ## `onedrive.search_items`
27
+
28
+ Searches OneDrive for files and folders.
29
+
30
+ ### Parameters
31
+
32
+ * `query` (string, required): Search query.
33
+ * `top` (number, optional): Maximum number of items to return.
34
+ * `select` (string, optional): Optional OData `$select` clause.
35
+
36
+ ## `onedrive.create_folder`
37
+
38
+ Creates a folder in OneDrive root or under a specific parent folder.
39
+
40
+ ### Parameters
41
+
42
+ * `name` (string, required): Folder name.
43
+ * `parentPath` (string, optional): Parent folder path relative to OneDrive root.
44
+ * `parentItemId` (string, optional): Parent drive item ID.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frustrated/ms-graph-mcp",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "A JSR-based TypeScript MCP package for personal Microsoft Graph access via CLI",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -21,6 +21,8 @@
21
21
  "@microsoft/microsoft-graph-client": "npm:@microsoft/microsoft-graph-client@^3.0.0",
22
22
  "@commander-js/extra-typings": "npm:@commander-js/extra-typings@^14.0.0",
23
23
  "@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.28.0",
24
+ "zod/v3": "./src/vendor/zod-v3.ts",
25
+ "zod/v4": "./src/vendor/zod-v4.ts",
24
26
  "zod": "npm:zod@^3.24.2"
25
27
  },
26
28
  "dependencies": {
package/src/auth.ts CHANGED
@@ -18,7 +18,9 @@ const MSAL_CONFIG: Configuration = {
18
18
  system: {
19
19
  loggerOptions: {
20
20
  loggerCallback(loglevel, message, containsPii) {
21
- console.log(message);
21
+ if (!containsPii) {
22
+ process.stderr.write(`${message}\n`);
23
+ }
22
24
  },
23
25
  piiLoggingEnabled: false,
24
26
  logLevel: LogLevel.Verbose,
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ const program = new Command();
9
9
  program
10
10
  .name("ms-graph-mcp")
11
11
  .description("CLI for JSR-based Microsoft Graph MCP package")
12
- .version("0.1.0");
12
+ .version("0.1.11");
13
13
 
14
14
  program
15
15
  .command("init")
@@ -7,6 +7,7 @@ import { log } from './utils.ts';
7
7
  import { Client } from '@microsoft/microsoft-graph-client';
8
8
  import * as mail from './tools/mail.ts';
9
9
  import * as calendar from './tools/calendar.ts';
10
+ import * as onedrive from './tools/onedrive.ts';
10
11
 
11
12
  function buildGraphClient(accessToken: string): Client {
12
13
  return Client.init({
@@ -17,7 +18,7 @@ function buildGraphClient(accessToken: string): Client {
17
18
  export async function startMcpServer(): Promise<void> {
18
19
  const server = new McpServer({
19
20
  name: 'ms-graph-mcp',
20
- version: '0.1.10',
21
+ version: '0.1.11',
21
22
  });
22
23
 
23
24
  if (isToolEnabled('mail.list_messages')) {
@@ -66,6 +67,75 @@ export async function startMcpServer(): Promise<void> {
66
67
  );
67
68
  }
68
69
 
70
+ if (isToolEnabled('onedrive.list_items')) {
71
+ server.tool(
72
+ 'onedrive.list_items',
73
+ 'List files and folders from OneDrive root or a specific folder',
74
+ {
75
+ folderPath: z.string().trim().optional().describe('Folder path relative to OneDrive root, e.g. /Projects'),
76
+ itemId: z.string().trim().optional().describe('Drive item ID to list children from'),
77
+ top: z.number().int().min(1).max(999).optional().describe('Maximum number of items to return'),
78
+ select: z.string().trim().optional().describe('Optional OData $select clause'),
79
+ },
80
+ async (input) => {
81
+ const token = await getAccessToken();
82
+ const result = await onedrive.listItems(buildGraphClient(token), input);
83
+ return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
84
+ },
85
+ );
86
+ }
87
+
88
+ if (isToolEnabled('onedrive.get_item')) {
89
+ server.tool(
90
+ 'onedrive.get_item',
91
+ 'Get metadata for a OneDrive file or folder by path or item ID',
92
+ {
93
+ path: z.string().trim().optional().describe('Item path relative to OneDrive root, e.g. /Projects/report.docx'),
94
+ itemId: z.string().trim().optional().describe('Drive item ID'),
95
+ select: z.string().trim().optional().describe('Optional OData $select clause'),
96
+ },
97
+ async (input) => {
98
+ const token = await getAccessToken();
99
+ const result = await onedrive.getItem(buildGraphClient(token), input);
100
+ return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
101
+ },
102
+ );
103
+ }
104
+
105
+ if (isToolEnabled('onedrive.search_items')) {
106
+ server.tool(
107
+ 'onedrive.search_items',
108
+ 'Search OneDrive for files and folders',
109
+ {
110
+ query: z.string().trim().min(1).describe('Search query'),
111
+ top: z.number().int().min(1).max(999).optional().describe('Maximum number of items to return'),
112
+ select: z.string().trim().optional().describe('Optional OData $select clause'),
113
+ },
114
+ async (input) => {
115
+ const token = await getAccessToken();
116
+ const result = await onedrive.searchItems(buildGraphClient(token), input);
117
+ return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
118
+ },
119
+ );
120
+ }
121
+
122
+ if (isToolEnabled('onedrive.create_folder')) {
123
+ server.tool(
124
+ 'onedrive.create_folder',
125
+ 'Create a folder in OneDrive root or under a specific parent',
126
+ {
127
+ name: z.string().trim().min(1).describe('Folder name'),
128
+ parentPath: z.string().trim().optional().describe('Parent folder path relative to OneDrive root'),
129
+ parentItemId: z.string().trim().optional().describe('Parent drive item ID'),
130
+ },
131
+ async (input) => {
132
+ const token = await getAccessToken();
133
+ const result = await onedrive.createFolder(buildGraphClient(token), input);
134
+ return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
135
+ },
136
+ );
137
+ }
138
+
69
139
  const transport = new StdioServerTransport();
70
140
  await server.connect(transport);
71
141
  log('MCP server started (JSON-RPC 2.0 over stdio). Listening for commands...');
@@ -1,5 +1,23 @@
1
1
  import { Client } from '@microsoft/microsoft-graph-client';
2
- import { log, error } from '../utils.ts';
2
+ import { log, error, describeGraphError } from '../utils.ts';
3
+
4
+ function assertValidEventTimeRange(start: { dateTime: string; timeZone: string }, end: { dateTime: string; timeZone: string }): void {
5
+ const startDate = new Date(start.dateTime);
6
+ const endDate = new Date(end.dateTime);
7
+
8
+ if (Number.isNaN(startDate.getTime())) {
9
+ throw new Error(`Invalid start.dateTime value: ${start.dateTime}`);
10
+ }
11
+ if (Number.isNaN(endDate.getTime())) {
12
+ throw new Error(`Invalid end.dateTime value: ${end.dateTime}`);
13
+ }
14
+ if (endDate.getTime() <= startDate.getTime()) {
15
+ throw new Error('Event end time must be after the start time.');
16
+ }
17
+ if (!start.timeZone.trim() || !end.timeZone.trim()) {
18
+ throw new Error('Both start.timeZone and end.timeZone are required.');
19
+ }
20
+ }
3
21
 
4
22
  export async function createEvent(graphClient: Client, input: {
5
23
  subject: string;
@@ -10,6 +28,8 @@ export async function createEvent(graphClient: Client, input: {
10
28
  location?: string;
11
29
  }): Promise<any> {
12
30
  try {
31
+ assertValidEventTimeRange(input.start, input.end);
32
+
13
33
  const event = {
14
34
  subject: input.subject,
15
35
  start: input.start,
@@ -31,11 +51,6 @@ export async function createEvent(graphClient: Client, input: {
31
51
  };
32
52
  } catch (err: any) {
33
53
  error('Error creating calendar event:', err);
34
- return {
35
- id: null,
36
- webLink: null,
37
- status: 'failed',
38
- errorMessage: err.message,
39
- };
54
+ throw new Error(`Failed to create calendar event: ${describeGraphError(err)}`);
40
55
  }
41
56
  }
@@ -1,9 +1,12 @@
1
1
  import * as mail from './mail.ts';
2
2
  import * as calendar from './calendar.ts';
3
- // import * as onedrive from './onedrive'; // Placeholder for future tools
3
+ import * as onedrive from './onedrive.ts';
4
4
 
5
5
  export const tools = {
6
6
  'mail.list_messages': mail.listMessages,
7
7
  'calendar.create_event': calendar.createEvent,
8
- // 'onedrive.list_files': onedrive.listFiles,
8
+ 'onedrive.list_items': onedrive.listItems,
9
+ 'onedrive.get_item': onedrive.getItem,
10
+ 'onedrive.search_items': onedrive.searchItems,
11
+ 'onedrive.create_folder': onedrive.createFolder,
9
12
  };
package/src/tools/mail.ts CHANGED
@@ -1,10 +1,36 @@
1
1
  import { Client } from '@microsoft/microsoft-graph-client';
2
- import { log, error } from '../utils.ts';
2
+ import { log, error, describeGraphError } from '../utils.ts';
3
+
4
+ const WELL_KNOWN_FOLDERS = new Map<string, string>([
5
+ ['inbox', 'inbox'],
6
+ ['sent', 'sentitems'],
7
+ ['sentitems', 'sentitems'],
8
+ ['drafts', 'drafts'],
9
+ ['deleted', 'deleteditems'],
10
+ ['deleteditems', 'deleteditems'],
11
+ ['archive', 'archive'],
12
+ ['junk', 'junkemail'],
13
+ ['junkemail', 'junkemail'],
14
+ ['outbox', 'outbox'],
15
+ ]);
16
+
17
+ function normalizeFolderId(folderId?: string): string | undefined {
18
+ if (!folderId) {
19
+ return undefined;
20
+ }
21
+
22
+ const trimmed = folderId.trim();
23
+ if (!trimmed) {
24
+ return undefined;
25
+ }
26
+
27
+ return WELL_KNOWN_FOLDERS.get(trimmed.toLowerCase()) ?? trimmed;
28
+ }
3
29
 
4
30
  export async function listMessages(graphClient: Client, input: { folderId?: string; top?: number; filter?: string }): Promise<any> {
5
31
  try {
6
- let request = graphClient.api(input.folderId ? `/me/mailFolders/${input.folderId}/messages` :
7
- '/me/messages');
32
+ const folderId = normalizeFolderId(input.folderId);
33
+ let request = graphClient.api(folderId ? `/me/mailFolders/${folderId}/messages` : '/me/messages');
8
34
 
9
35
  if (input.top) {
10
36
  request = request.top(input.top);
@@ -29,6 +55,6 @@ export async function listMessages(graphClient: Client, input: { folderId?: stri
29
55
  };
30
56
  } catch (err: any) {
31
57
  error('Error listing messages:', err);
32
- throw err;
58
+ throw new Error(`Failed to list messages: ${describeGraphError(err)}`);
33
59
  }
34
60
  }
@@ -0,0 +1,145 @@
1
+ import { Client } from '@microsoft/microsoft-graph-client';
2
+ import { error, log, describeGraphError } from '../utils.ts';
3
+
4
+ type DriveItem = {
5
+ id: string;
6
+ name: string;
7
+ webUrl?: string;
8
+ size?: number;
9
+ createdDateTime?: string;
10
+ lastModifiedDateTime?: string;
11
+ folder?: unknown;
12
+ file?: unknown;
13
+ };
14
+
15
+ function trimLeadingSlash(value: string): string {
16
+ return value.replace(/^\/+/, '');
17
+ }
18
+
19
+ function toRootPath(path: string): string {
20
+ const normalized = trimLeadingSlash(path.trim());
21
+ const segments = normalized
22
+ .split('/')
23
+ .filter((segment) => segment.length > 0)
24
+ .map((segment) => encodeURIComponent(segment));
25
+ return `/${segments.join('/')}`;
26
+ }
27
+
28
+ function buildItemPath(itemId?: string, folderPath?: string): string {
29
+ if (itemId) {
30
+ return `/me/drive/items/${itemId}`;
31
+ }
32
+
33
+ if (folderPath) {
34
+ return `/me/drive/root:${toRootPath(folderPath)}:`;
35
+ }
36
+
37
+ return '/me/drive/root';
38
+ }
39
+
40
+ function simplifyItem(item: DriveItem): Record<string, unknown> {
41
+ return {
42
+ id: item.id,
43
+ name: item.name,
44
+ webUrl: item.webUrl,
45
+ size: item.size,
46
+ createdDateTime: item.createdDateTime,
47
+ lastModifiedDateTime: item.lastModifiedDateTime,
48
+ isFolder: Boolean(item.folder),
49
+ isFile: Boolean(item.file),
50
+ };
51
+ }
52
+
53
+ export async function listItems(
54
+ graphClient: Client,
55
+ input: { folderPath?: string; itemId?: string; top?: number; select?: string },
56
+ ): Promise<any> {
57
+ try {
58
+ const path = buildItemPath(input.itemId, input.folderPath);
59
+ let request = graphClient.api(`${path}/children`);
60
+
61
+ if (input.top) {
62
+ request = request.top(input.top);
63
+ }
64
+
65
+ const select = input.select?.trim();
66
+ const response = select
67
+ ? await request.select(select).get()
68
+ : await request.get();
69
+
70
+ log(`Listed ${response.value.length} OneDrive items.`);
71
+ return {
72
+ items: response.value.map((item: DriveItem) => simplifyItem(item)),
73
+ nextLink: response['@odata.nextLink'],
74
+ };
75
+ } catch (err: any) {
76
+ error('Error listing OneDrive items:', err);
77
+ throw new Error(`Failed to list OneDrive items: ${describeGraphError(err)}`);
78
+ }
79
+ }
80
+
81
+ export async function getItem(
82
+ graphClient: Client,
83
+ input: { itemId?: string; path?: string; select?: string },
84
+ ): Promise<any> {
85
+ try {
86
+ const path = buildItemPath(input.itemId, input.path);
87
+ const select = input.select?.trim();
88
+ const response = select
89
+ ? await graphClient.api(path).select(select).get()
90
+ : await graphClient.api(path).get();
91
+
92
+ log(`Fetched OneDrive item: ${response.name ?? response.id}`);
93
+ return simplifyItem(response);
94
+ } catch (err: any) {
95
+ error('Error retrieving OneDrive item:', err);
96
+ throw new Error(`Failed to retrieve OneDrive item: ${describeGraphError(err)}`);
97
+ }
98
+ }
99
+
100
+ export async function searchItems(
101
+ graphClient: Client,
102
+ input: { query: string; top?: number; select?: string },
103
+ ): Promise<any> {
104
+ try {
105
+ let request = graphClient.api(`/me/drive/root/search(q='${input.query.replace(/'/g, "''")}')`);
106
+
107
+ if (input.top) {
108
+ request = request.top(input.top);
109
+ }
110
+
111
+ const select = input.select?.trim();
112
+ const response = select
113
+ ? await request.select(select).get()
114
+ : await request.get();
115
+
116
+ log(`Search returned ${response.value.length} OneDrive items.`);
117
+ return {
118
+ items: response.value.map((item: DriveItem) => simplifyItem(item)),
119
+ nextLink: response['@odata.nextLink'],
120
+ };
121
+ } catch (err: any) {
122
+ error('Error searching OneDrive items:', err);
123
+ throw new Error(`Failed to search OneDrive items: ${describeGraphError(err)}`);
124
+ }
125
+ }
126
+
127
+ export async function createFolder(
128
+ graphClient: Client,
129
+ input: { name: string; parentPath?: string; parentItemId?: string },
130
+ ): Promise<any> {
131
+ try {
132
+ const parentPath = buildItemPath(input.parentItemId, input.parentPath);
133
+ const response = await graphClient.api(`${parentPath}/children`).post({
134
+ name: input.name,
135
+ folder: {},
136
+ '@microsoft.graph.conflictBehavior': 'rename',
137
+ });
138
+
139
+ log(`Created OneDrive folder: ${response.name} (ID: ${response.id})`);
140
+ return simplifyItem(response);
141
+ } catch (err: any) {
142
+ error('Error creating OneDrive folder:', err);
143
+ throw new Error(`Failed to create OneDrive folder: ${describeGraphError(err)}`);
144
+ }
145
+ }
package/src/utils.ts CHANGED
@@ -18,4 +18,32 @@ export function warn(message: string) {
18
18
  log(message, 'warn');
19
19
  }
20
20
 
21
- // Placeholder for future file-based logging or other utilities
21
+ export function describeGraphError(err: unknown): string {
22
+ if (err instanceof Error) {
23
+ const parts: string[] = [err.message];
24
+ const graphErr = err as Error & {
25
+ code?: string;
26
+ statusCode?: number;
27
+ body?: { error?: { code?: string; message?: string } };
28
+ response?: { status?: number; body?: { error?: { code?: string; message?: string } } };
29
+ };
30
+
31
+ const status = graphErr.statusCode ?? graphErr.response?.status;
32
+ const code = graphErr.code ?? graphErr.body?.error?.code ?? graphErr.response?.body?.error?.code;
33
+ const message = graphErr.body?.error?.message ?? graphErr.response?.body?.error?.message;
34
+
35
+ if (status) {
36
+ parts.unshift(`HTTP ${status}`);
37
+ }
38
+ if (code) {
39
+ parts.push(`code=${code}`);
40
+ }
41
+ if (message && message !== err.message) {
42
+ parts.push(message);
43
+ }
44
+
45
+ return parts.join(' | ');
46
+ }
47
+
48
+ return String(err);
49
+ }
@@ -0,0 +1,4 @@
1
+ import * as zod from 'zod';
2
+
3
+ export * from 'zod';
4
+ export default zod;
@@ -0,0 +1,4 @@
1
+ import * as zod from 'zod';
2
+
3
+ export * from 'zod';
4
+ export default zod;