Skip to content

feat: add zod tools validation support #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@
"lint:fix": "eslint . --ext .js,.ts --fix",
"format": "prettier --write \"**/*.{js,ts,json}\"",
"format:check": "prettier --check \"**/*.{js,ts,json}\"",
"generate-notice": "node utils/generate-notice.js",
"generate-notice": "node utils/generate-notice.js"
},
"keywords": [
"auth0",
@@ -41,7 +41,9 @@
"jwt-decode": "^4.0.0",
"keytar": "^7.9.0",
"open": "^10.1.0",
"which": "^5.0.0"
"which": "^5.0.0",
"zod": "^3.24.2",
"zod-to-json-schema": "^3.24.5"
},
"devDependencies": {
"@eslint/js": "^9.23.0",
3 changes: 0 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -6,9 +6,6 @@ import logout from './commands/logout.js';
import session from './commands/session.js';
import { logError } from './utils/logger.js';

// Enable all debug logs for this package by default
//process.env.DEBUG = (process.env.DEBUG || '') + ',auth0-mcp:*';

// Set process title
process.title = 'auth0-mcp-server';

13 changes: 12 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,9 @@ import { HANDLERS, TOOLS } from './tools/index.js';
import { log, logInfo } from './utils/logger.js';
import { formatDomain } from './utils/http-utility.js';
import { maskTenantName } from './utils/cli-utility.js';
import { zodToJsonSchema } from 'zod-to-json-schema';
import type { ZodSchema } from 'zod';
import type { ToolSchema } from './utils/types.js';

// Server implementation
export async function startServer() {
@@ -37,7 +40,15 @@ export async function startServer() {

// Sanitize tools by removing _meta fields
// See: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/264
const filteredTools = TOOLS.map(({ _meta, ...rest }) => rest);
const filteredTools = TOOLS.map(({ _meta, ...tool }) => {
// Convert Zod schema to JSON schema
const toolSchema = zodToJsonSchema(tool.inputSchema as ZodSchema) as ToolSchema;
return {
name: tool.name,
description: tool.description,
inputSchema: toolSchema,
};
});
Comment on lines +43 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should do this processing outside the request handler in a processTools helper to prevent it from happening on each request. I know, I introduced this pattern, my bad!


return { tools: filteredTools };
});
233 changes: 86 additions & 147 deletions src/tools/actions.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import { createErrorResponse, createSuccessResponse } from '../utils/http-utilit
import type { Auth0Config } from '../utils/config.js';
import { getManagementClient } from '../utils/management-client.js';
import type { PatchActionRequest, PostActionRequest } from 'auth0/dist/cjs/management/index.js';
import { z } from 'zod';

interface Auth0Action {
id: string;
@@ -37,181 +38,119 @@ export const ACTION_TOOLS: Tool[] = [
{
name: 'auth0_list_actions',
description: 'List all actions in the Auth0 tenant',
inputSchema: {
type: 'object',
properties: {
page: { type: 'number', description: 'Page number (0-based)' },
per_page: { type: 'number', description: 'Number of actions per page' },
include_totals: { type: 'boolean', description: 'Include total count' },
trigger_id: { type: 'string', description: 'Filter by trigger ID' },
},
},
inputSchema: z.object({
page: z.number().optional().describe('Page number (0-based)'),
per_page: z.number().optional().describe('Number of actions per page'),
include_totals: z.boolean().optional().describe('Include total count'),
trigger_id: z.string().optional().describe('Filter by trigger ID'),
}),
_meta: {
requiredScopes: ['read:actions'],
},
},
{
name: 'auth0_get_action',
description: 'Get details about a specific Auth0 action',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'ID of the action to retrieve' },
},
required: ['id'],
},
inputSchema: z.object({
id: z.string().describe('ID of the action to retrieve'),
}),
_meta: {
requiredScopes: ['read:actions'],
},
},
{
name: 'auth0_create_action',
description: 'Create a new Auth0 action',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the action. Required.',
},
supported_triggers: {
type: 'array',
description: 'The list of triggers that this action supports. Required.',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'ID of the trigger' },
version: { type: 'string', description: 'Version of the trigger (e.g., "v2")' },
},
required: ['id', 'version'],
},
},
code: {
type: 'string',
description: 'The source code of the action. Required.',
},
runtime: {
type: 'string',
description: 'The Node runtime. For example: "node18" or "node16". Defaults to "node18".',
},
dependencies: {
type: 'array',
description: 'List of third party npm modules that this action depends on.',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Name of the NPM package' },
version: { type: 'string', description: 'Version of the NPM package' },
},
required: ['name', 'version'],
},
},
secrets: {
type: 'array',
description: 'List of secrets that are included in the action.',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Name of the secret' },
value: { type: 'string', description: 'Value of the secret' },
},
required: ['name', 'value'],
},
},
},
required: ['name', 'supported_triggers'],
},
inputSchema: z.object({
name: z.string().describe('Name of the action. Required.'),
supported_triggers: z
.array(
z.object({
id: z.string().describe('ID of the trigger'),
version: z.string().describe('Version of the trigger (e.g., "v2")'),
})
)
.describe('The list of triggers that this action supports. Required.'),
code: z.string().describe('The source code of the action. Required.'),
runtime: z
.string()
.optional()
.describe('The Node runtime. For example: "node18" or "node16". Defaults to "node18".'),
dependencies: z
.array(
z.object({
name: z.string().describe('Name of the NPM package'),
version: z.string().describe('Version of the NPM package'),
})
)
.optional()
.describe('List of third party npm modules that this action depends on.'),
secrets: z
.array(
z.object({
name: z.string().describe('Name of the secret'),
value: z.string().describe('Value of the secret'),
})
)
.optional()
.describe('List of secrets that are included in the action.'),
}),
_meta: {
requiredScopes: ['create:actions'],
},
},
{
name: 'auth0_update_action',
description: 'Update an existing Auth0 action',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'ID of the action to update. Required.',
},
name: {
type: 'string',
description: 'New name of the action. Optional.',
},
supported_triggers: {
type: 'array',
description: 'The list of triggers that this action supports. Optional.',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'ID of the trigger' },
version: { type: 'string', description: 'Version of the trigger (e.g., "v2")' },
},
required: ['id', 'version'],
},
},
code: {
type: 'string',
description: 'New JavaScript code for the action. Optional.',
},
runtime: {
type: 'string',
description: 'The Node runtime. For example: "node18" or "node16".',
},
dependencies: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the NPM dependency',
},
version: {
type: 'string',
description: 'Version of the NPM dependency',
},
},
required: ['name', 'version'],
},
description: 'Updated NPM dependencies for the action. Optional.',
},
secrets: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the secret variable',
},
value: {
type: 'string',
description: 'Value of the secret. If omitted, the existing value is retained.',
},
},
required: ['name'],
},
description: 'Secrets to update for the action. Optional.',
},
},
required: ['id'],
},
inputSchema: z.object({
id: z.string().describe('ID of the action to update. Required.'),
name: z.string().optional().describe('New name of the action. Optional.'),
supported_triggers: z
.array(
z.object({
id: z.string().describe('ID of the trigger'),
version: z.string().describe('Version of the trigger (e.g., "v2")'),
})
)
.optional()
.describe('The list of triggers that this action supports. Optional.'),
code: z.string().optional().describe('New JavaScript code for the action. Optional.'),
runtime: z
.string()
.optional()
.describe('The Node runtime. For example: "node18" or "node16".'),
dependencies: z
.array(
z.object({
name: z.string().describe('Name of the NPM dependency'),
version: z.string().describe('Version of the NPM dependency'),
})
)
.optional()
.describe('Updated NPM dependencies for the action. Optional.'),
secrets: z
.array(
z.object({
name: z.string().describe('Name of the secret variable'),
value: z
.string()
.optional()
.describe('Value of the secret. If omitted, the existing value is retained.'),
})
)
.optional()
.describe('Secrets to update for the action. Optional.'),
}),
_meta: {
requiredScopes: ['update:actions'],
},
},
{
name: 'auth0_deploy_action',
description: 'Deploy an Auth0 action',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'ID of the action to deploy' },
},
required: ['id'],
},
inputSchema: z.object({
id: z.string().describe('ID of the action to deploy'),
}),
_meta: {
requiredScopes: ['update:actions'],
},
Loading