feat(tools): added clerk tools and block (#3032)

* feat(tools): added clerk tools and block

* updated docs gen script

* use clerk api types
This commit is contained in:
Waleed
2026-01-27 16:45:48 -08:00
committed by GitHub
parent 089427822e
commit bca355c36d
48 changed files with 3288 additions and 744 deletions

View File

@@ -0,0 +1,393 @@
import { ClerkIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import type { ClerkResponse } from '@/tools/clerk/types'
export const ClerkBlock: BlockConfig<ClerkResponse> = {
type: 'clerk',
name: 'Clerk',
description: 'Manage users, organizations, and sessions in Clerk',
longDescription:
'Integrate Clerk authentication and user management into your workflow. Create, update, delete, and list users. Manage organizations and their memberships. Monitor and control user sessions.',
docsLink: 'https://docs.sim.ai/tools/clerk',
category: 'tools',
bgColor: '#131316',
icon: ClerkIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'List Users', id: 'clerk_list_users' },
{ label: 'Get User', id: 'clerk_get_user' },
{ label: 'Create User', id: 'clerk_create_user' },
{ label: 'Update User', id: 'clerk_update_user' },
{ label: 'Delete User', id: 'clerk_delete_user' },
{ label: 'List Organizations', id: 'clerk_list_organizations' },
{ label: 'Get Organization', id: 'clerk_get_organization' },
{ label: 'Create Organization', id: 'clerk_create_organization' },
{ label: 'List Sessions', id: 'clerk_list_sessions' },
{ label: 'Get Session', id: 'clerk_get_session' },
{ label: 'Revoke Session', id: 'clerk_revoke_session' },
],
value: () => 'clerk_list_users',
},
{
id: 'secretKey',
title: 'Secret Key',
type: 'short-input',
password: true,
placeholder: 'sk_live_... or sk_test_...',
required: true,
},
// List Users params
{
id: 'query',
title: 'Search Query',
type: 'short-input',
placeholder: 'Search by email, phone, username, or name',
condition: { field: 'operation', value: 'clerk_list_users' },
},
{
id: 'emailAddressFilter',
title: 'Email Filter',
type: 'short-input',
placeholder: 'Filter by email (comma-separated)',
condition: { field: 'operation', value: 'clerk_list_users' },
},
{
id: 'usernameFilter',
title: 'Username Filter',
type: 'short-input',
placeholder: 'Filter by username (comma-separated)',
condition: { field: 'operation', value: 'clerk_list_users' },
},
// Get User params
{
id: 'userId',
title: 'User ID',
type: 'short-input',
placeholder: 'user_...',
condition: {
field: 'operation',
value: ['clerk_get_user', 'clerk_update_user', 'clerk_delete_user'],
},
required: {
field: 'operation',
value: ['clerk_get_user', 'clerk_update_user', 'clerk_delete_user'],
},
},
// Create/Update User params
{
id: 'emailAddress',
title: 'Email Address',
type: 'short-input',
placeholder: 'user@example.com (comma-separated for multiple)',
condition: { field: 'operation', value: 'clerk_create_user' },
},
{
id: 'phoneNumber',
title: 'Phone Number',
type: 'short-input',
placeholder: '+1234567890 (comma-separated for multiple)',
condition: { field: 'operation', value: 'clerk_create_user' },
},
{
id: 'username',
title: 'Username',
type: 'short-input',
placeholder: 'johndoe',
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
},
{
id: 'password',
title: 'Password',
type: 'short-input',
password: true,
placeholder: 'Minimum 8 characters',
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
},
{
id: 'firstName',
title: 'First Name',
type: 'short-input',
placeholder: 'John',
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
},
{
id: 'lastName',
title: 'Last Name',
type: 'short-input',
placeholder: 'Doe',
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
},
{
id: 'externalId',
title: 'External ID',
type: 'short-input',
placeholder: 'Your system user ID',
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
},
{
id: 'publicMetadata',
title: 'Public Metadata',
type: 'code',
language: 'json',
placeholder: '{"role": "admin"}',
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
},
{
id: 'privateMetadata',
title: 'Private Metadata',
type: 'code',
language: 'json',
placeholder: '{"internalId": "123"}',
condition: { field: 'operation', value: ['clerk_create_user', 'clerk_update_user'] },
},
// Organization params
{
id: 'orgQuery',
title: 'Search Query',
type: 'short-input',
placeholder: 'Search by name, ID, or slug',
condition: { field: 'operation', value: 'clerk_list_organizations' },
},
{
id: 'includeMembersCount',
title: 'Include Members Count',
type: 'switch',
condition: { field: 'operation', value: 'clerk_list_organizations' },
},
{
id: 'organizationId',
title: 'Organization ID',
type: 'short-input',
placeholder: 'org_... or slug',
condition: { field: 'operation', value: 'clerk_get_organization' },
required: { field: 'operation', value: 'clerk_get_organization' },
},
{
id: 'orgName',
title: 'Organization Name',
type: 'short-input',
placeholder: 'Acme Corp',
condition: { field: 'operation', value: 'clerk_create_organization' },
required: { field: 'operation', value: 'clerk_create_organization' },
},
{
id: 'createdBy',
title: 'Creator User ID',
type: 'short-input',
placeholder: 'user_... (will become admin)',
condition: { field: 'operation', value: 'clerk_create_organization' },
required: { field: 'operation', value: 'clerk_create_organization' },
},
{
id: 'slug',
title: 'Slug',
type: 'short-input',
placeholder: 'acme-corp',
condition: { field: 'operation', value: 'clerk_create_organization' },
},
{
id: 'maxAllowedMemberships',
title: 'Max Members',
type: 'short-input',
placeholder: '0 for unlimited',
condition: { field: 'operation', value: 'clerk_create_organization' },
},
// Session params
{
id: 'sessionUserId',
title: 'User ID',
type: 'short-input',
placeholder: 'user_...',
condition: { field: 'operation', value: 'clerk_list_sessions' },
},
{
id: 'clientId',
title: 'Client ID',
type: 'short-input',
placeholder: 'client_...',
condition: { field: 'operation', value: 'clerk_list_sessions' },
},
{
id: 'sessionStatus',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Active', id: 'active' },
{ label: 'Ended', id: 'ended' },
{ label: 'Expired', id: 'expired' },
{ label: 'Revoked', id: 'revoked' },
{ label: 'Abandoned', id: 'abandoned' },
{ label: 'Pending', id: 'pending' },
],
value: () => '',
condition: { field: 'operation', value: 'clerk_list_sessions' },
},
{
id: 'sessionId',
title: 'Session ID',
type: 'short-input',
placeholder: 'sess_...',
condition: { field: 'operation', value: ['clerk_get_session', 'clerk_revoke_session'] },
required: { field: 'operation', value: ['clerk_get_session', 'clerk_revoke_session'] },
},
// Pagination params (common)
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: 'Results per page (1-500, default: 10)',
condition: {
field: 'operation',
value: ['clerk_list_users', 'clerk_list_organizations', 'clerk_list_sessions'],
},
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: 'Skip N results for pagination',
condition: {
field: 'operation',
value: ['clerk_list_users', 'clerk_list_organizations', 'clerk_list_sessions'],
},
},
],
tools: {
access: [
'clerk_list_users',
'clerk_get_user',
'clerk_create_user',
'clerk_update_user',
'clerk_delete_user',
'clerk_list_organizations',
'clerk_get_organization',
'clerk_create_organization',
'clerk_list_sessions',
'clerk_get_session',
'clerk_revoke_session',
],
config: {
tool: (params) => params.operation as string,
params: (params) => {
const {
operation,
secretKey,
emailAddressFilter,
usernameFilter,
orgQuery,
orgName,
sessionUserId,
sessionStatus,
publicMetadata,
privateMetadata,
maxAllowedMemberships,
...rest
} = params
const cleanParams: Record<string, unknown> = {
secretKey,
}
// Map UI fields to API params based on operation
switch (operation) {
case 'clerk_list_users':
if (emailAddressFilter) cleanParams.emailAddress = emailAddressFilter
if (usernameFilter) cleanParams.username = usernameFilter
break
case 'clerk_create_user':
case 'clerk_update_user':
if (publicMetadata) {
cleanParams.publicMetadata =
typeof publicMetadata === 'string' ? JSON.parse(publicMetadata) : publicMetadata
}
if (privateMetadata) {
cleanParams.privateMetadata =
typeof privateMetadata === 'string' ? JSON.parse(privateMetadata) : privateMetadata
}
break
case 'clerk_list_organizations':
if (orgQuery) cleanParams.query = orgQuery
break
case 'clerk_create_organization':
if (orgName) cleanParams.name = orgName
if (maxAllowedMemberships)
cleanParams.maxAllowedMemberships = Number(maxAllowedMemberships)
break
case 'clerk_list_sessions':
if (sessionUserId) cleanParams.userId = sessionUserId
if (sessionStatus) cleanParams.status = sessionStatus
break
}
// Add remaining params that don't need mapping
Object.entries(rest).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
cleanParams[key] = value
}
})
return cleanParams
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
secretKey: { type: 'string', description: 'Clerk Secret Key' },
userId: { type: 'string', description: 'User ID' },
organizationId: { type: 'string', description: 'Organization ID or slug' },
sessionId: { type: 'string', description: 'Session ID' },
query: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Results per page' },
offset: { type: 'number', description: 'Pagination offset' },
},
outputs: {
// List outputs (arrays stored as json for block compatibility)
users: { type: 'json', description: 'Array of user objects' },
organizations: { type: 'json', description: 'Array of organization objects' },
sessions: { type: 'json', description: 'Array of session objects' },
// Single entity fields (destructured from get/create/update operations)
id: { type: 'string', description: 'Resource ID (user, organization, or session)' },
name: { type: 'string', description: 'Organization name' },
slug: { type: 'string', description: 'Organization slug' },
username: { type: 'string', description: 'Username' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
imageUrl: { type: 'string', description: 'Profile image URL' },
hasImage: { type: 'boolean', description: 'Whether resource has an image' },
emailAddresses: { type: 'json', description: 'User email addresses' },
phoneNumbers: { type: 'json', description: 'User phone numbers' },
primaryEmailAddressId: { type: 'string', description: 'Primary email address ID' },
primaryPhoneNumberId: { type: 'string', description: 'Primary phone number ID' },
externalId: { type: 'string', description: 'External system ID' },
passwordEnabled: { type: 'boolean', description: 'Whether password is enabled' },
twoFactorEnabled: { type: 'boolean', description: 'Whether 2FA is enabled' },
banned: { type: 'boolean', description: 'Whether user is banned' },
locked: { type: 'boolean', description: 'Whether user is locked' },
userId: { type: 'string', description: 'User ID (for sessions)' },
clientId: { type: 'string', description: 'Client ID (for sessions)' },
status: { type: 'string', description: 'Session status' },
lastActiveAt: { type: 'number', description: 'Last activity timestamp' },
lastSignInAt: { type: 'number', description: 'Last sign-in timestamp' },
membersCount: { type: 'number', description: 'Number of members' },
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
createdBy: { type: 'string', description: 'Creator user ID' },
publicMetadata: { type: 'json', description: 'Public metadata' },
// Common outputs
totalCount: { type: 'number', description: 'Total count for paginated results' },
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
object: { type: 'string', description: 'Object type' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -13,6 +13,7 @@ import { CalendlyBlock } from '@/blocks/blocks/calendly'
import { ChatTriggerBlock } from '@/blocks/blocks/chat_trigger'
import { CirclebackBlock } from '@/blocks/blocks/circleback'
import { ClayBlock } from '@/blocks/blocks/clay'
import { ClerkBlock } from '@/blocks/blocks/clerk'
import { ConditionBlock } from '@/blocks/blocks/condition'
import { ConfluenceBlock, ConfluenceV2Block } from '@/blocks/blocks/confluence'
import { CursorBlock, CursorV2Block } from '@/blocks/blocks/cursor'
@@ -168,6 +169,7 @@ export const registry: Record<string, BlockConfig> = {
chat_trigger: ChatTriggerBlock,
circleback: CirclebackBlock,
clay: ClayBlock,
clerk: ClerkBlock,
condition: ConditionBlock,
confluence: ConfluenceBlock,
confluence_v2: ConfluenceV2Block,

View File

@@ -2096,6 +2096,23 @@ export function ClayIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function ClerkIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 128 128' fill='none' xmlns='http://www.w3.org/2000/svg'>
<circle cx='64' cy='64' r='20' fill='white' />
<path
d='M99.5716 10.788C101.571 12.1272 101.742 14.9444 100.04 16.646L85.4244 31.2618C84.1035 32.5828 82.0542 32.7914 80.3915 31.9397C75.4752 29.421 69.9035 28 64 28C44.1177 28 28 44.1177 28 64C28 69.9035 29.421 75.4752 31.9397 80.3915C32.7914 82.0542 32.5828 84.1035 31.2618 85.4244L16.646 100.04C14.9444 101.742 12.1272 101.571 10.788 99.5716C3.97411 89.3989 0 77.1635 0 64C0 28.6538 28.6538 0 64 0C77.1635 0 89.3989 3.97411 99.5716 10.788Z'
fill='white'
fillOpacity='0.4'
/>
<path
d='M100.04 111.354C101.742 113.056 101.571 115.873 99.5717 117.212C89.3989 124.026 77.1636 128 64 128C50.8364 128 38.6011 124.026 28.4283 117.212C26.4289 115.873 26.2581 113.056 27.9597 111.354L42.5755 96.7382C43.8965 95.4172 45.9457 95.2085 47.6084 96.0603C52.5248 98.579 58.0964 100 64 100C69.9036 100 75.4753 98.579 80.3916 96.0603C82.0543 95.2085 84.1036 95.4172 85.4245 96.7382L100.04 111.354Z'
fill='white'
/>
</svg>
)
}
export function MicrosoftIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 23 23' {...props}>

View File

@@ -0,0 +1,146 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkCreateOrganizationParams,
ClerkCreateOrganizationResponse,
ClerkOrganization,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkCreateOrganization')
export const clerkCreateOrganizationTool: ToolConfig<
ClerkCreateOrganizationParams,
ClerkCreateOrganizationResponse
> = {
id: 'clerk_create_organization',
name: 'Create Organization in Clerk',
description: 'Create a new organization in your Clerk application',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the organization',
},
createdBy: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID of the creator (will become admin)',
},
slug: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Slug identifier for the organization',
},
maxAllowedMemberships: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum member capacity (0 for unlimited)',
},
publicMetadata: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Public metadata (JSON object)',
},
privateMetadata: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Private metadata (JSON object)',
},
},
request: {
url: () => 'https://api.clerk.com/v1/organizations',
method: 'POST',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
const body: Record<string, unknown> = {
name: params.name,
created_by: params.createdBy,
}
if (params.slug) body.slug = params.slug
if (params.maxAllowedMemberships !== undefined)
body.max_allowed_memberships = params.maxAllowedMemberships
if (params.publicMetadata) body.public_metadata = params.publicMetadata
if (params.privateMetadata) body.private_metadata = params.privateMetadata
return body
},
},
transformResponse: async (response: Response) => {
const data: ClerkOrganization | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to create organization in Clerk'
)
}
const org = data as ClerkOrganization
return {
success: true,
output: {
id: org.id,
name: org.name,
slug: org.slug ?? null,
imageUrl: org.image_url ?? null,
hasImage: org.has_image ?? false,
membersCount: org.members_count ?? null,
pendingInvitationsCount: org.pending_invitations_count ?? null,
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
adminDeleteEnabled: org.admin_delete_enabled ?? false,
createdBy: org.created_by ?? null,
createdAt: org.created_at,
updatedAt: org.updated_at,
publicMetadata: org.public_metadata ?? {},
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Created organization ID' },
name: { type: 'string', description: 'Organization name' },
slug: { type: 'string', description: 'Organization slug', optional: true },
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
membersCount: { type: 'number', description: 'Number of members', optional: true },
pendingInvitationsCount: {
type: 'number',
description: 'Number of pending invitations',
optional: true,
},
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
publicMetadata: { type: 'json', description: 'Public metadata' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,227 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkCreateUserParams,
ClerkCreateUserResponse,
ClerkEmailAddress,
ClerkPhoneNumber,
ClerkUser,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkCreateUser')
export const clerkCreateUserTool: ToolConfig<ClerkCreateUserParams, ClerkCreateUserResponse> = {
id: 'clerk_create_user',
name: 'Create User in Clerk',
description: 'Create a new user in your Clerk application',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
emailAddress: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email addresses for the user (comma-separated for multiple)',
},
phoneNumber: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Phone numbers for the user (comma-separated for multiple)',
},
username: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Username for the user (must be unique)',
},
password: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Password for the user (minimum 8 characters)',
},
firstName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'First name of the user',
},
lastName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Last name of the user',
},
externalId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'External system identifier (must be unique)',
},
publicMetadata: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Public metadata (JSON object, readable from frontend)',
},
privateMetadata: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Private metadata (JSON object, backend only)',
},
unsafeMetadata: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Unsafe metadata (JSON object, modifiable from frontend)',
},
skipPasswordChecks: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Skip password validation checks',
},
skipPasswordRequirement: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Make password optional',
},
},
request: {
url: () => 'https://api.clerk.com/v1/users',
method: 'POST',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
const body: Record<string, unknown> = {}
if (params.emailAddress) {
const emailStr = params.emailAddress as string
body.email_address = emailStr.split(',').map((e) => e.trim())
}
if (params.phoneNumber) {
const phoneStr = params.phoneNumber as string
body.phone_number = phoneStr.split(',').map((p) => p.trim())
}
if (params.username) body.username = params.username.trim()
if (params.password) body.password = params.password
if (params.firstName) body.first_name = params.firstName.trim()
if (params.lastName) body.last_name = params.lastName.trim()
if (params.externalId) body.external_id = params.externalId.trim()
if (params.publicMetadata) body.public_metadata = params.publicMetadata
if (params.privateMetadata) body.private_metadata = params.privateMetadata
if (params.unsafeMetadata) body.unsafe_metadata = params.unsafeMetadata
if (params.skipPasswordChecks !== undefined)
body.skip_password_checks = params.skipPasswordChecks
if (params.skipPasswordRequirement !== undefined)
body.skip_password_requirement = params.skipPasswordRequirement
return body
},
},
transformResponse: async (response: Response) => {
const data: ClerkUser | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to create user in Clerk'
)
}
const user = data as ClerkUser
return {
success: true,
output: {
id: user.id,
username: user.username ?? null,
firstName: user.first_name ?? null,
lastName: user.last_name ?? null,
imageUrl: user.image_url ?? null,
primaryEmailAddressId: user.primary_email_address_id ?? null,
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
id: email.id,
emailAddress: email.email_address,
verified: email.verification?.status === 'verified',
})),
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
id: phone.id,
phoneNumber: phone.phone_number,
verified: phone.verification?.status === 'verified',
})),
externalId: user.external_id ?? null,
createdAt: user.created_at,
updatedAt: user.updated_at,
publicMetadata: user.public_metadata ?? {},
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Created user ID' },
username: { type: 'string', description: 'Username', optional: true },
firstName: { type: 'string', description: 'First name', optional: true },
lastName: { type: 'string', description: 'Last name', optional: true },
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
primaryEmailAddressId: {
type: 'string',
description: 'Primary email address ID',
optional: true,
},
primaryPhoneNumberId: {
type: 'string',
description: 'Primary phone number ID',
optional: true,
},
emailAddresses: {
type: 'array',
description: 'User email addresses',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Email address ID' },
emailAddress: { type: 'string', description: 'Email address' },
verified: { type: 'boolean', description: 'Whether email is verified' },
},
},
},
phoneNumbers: {
type: 'array',
description: 'User phone numbers',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Phone number ID' },
phoneNumber: { type: 'string', description: 'Phone number' },
verified: { type: 'boolean', description: 'Whether phone is verified' },
},
},
},
externalId: { type: 'string', description: 'External system ID', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
publicMetadata: { type: 'json', description: 'Public metadata' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,75 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkDeleteResponse,
ClerkDeleteUserParams,
ClerkDeleteUserResponse,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkDeleteUser')
export const clerkDeleteUserTool: ToolConfig<ClerkDeleteUserParams, ClerkDeleteUserResponse> = {
id: 'clerk_delete_user',
name: 'Delete User from Clerk',
description: 'Delete a user from your Clerk application',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the user to delete',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
method: 'DELETE',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
},
transformResponse: async (response: Response) => {
const data: ClerkDeleteResponse | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to delete user from Clerk'
)
}
const deleteData = data as ClerkDeleteResponse
return {
success: true,
output: {
id: deleteData.id,
object: deleteData.object ?? 'user',
deleted: deleteData.deleted ?? true,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Deleted user ID' },
object: { type: 'string', description: 'Object type (user)' },
deleted: { type: 'boolean', description: 'Whether the user was deleted' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,102 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkGetOrganizationParams,
ClerkGetOrganizationResponse,
ClerkOrganization,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkGetOrganization')
export const clerkGetOrganizationTool: ToolConfig<
ClerkGetOrganizationParams,
ClerkGetOrganizationResponse
> = {
id: 'clerk_get_organization',
name: 'Get Organization from Clerk',
description: 'Retrieve a single organization by ID or slug from Clerk',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
organizationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID or slug of the organization to retrieve',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/organizations/${params.organizationId}`,
method: 'GET',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
},
transformResponse: async (response: Response) => {
const data: ClerkOrganization | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get organization from Clerk'
)
}
const org = data as ClerkOrganization
return {
success: true,
output: {
id: org.id,
name: org.name,
slug: org.slug ?? null,
imageUrl: org.image_url ?? null,
hasImage: org.has_image ?? false,
membersCount: org.members_count ?? null,
pendingInvitationsCount: org.pending_invitations_count ?? null,
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
adminDeleteEnabled: org.admin_delete_enabled ?? false,
createdBy: org.created_by ?? null,
createdAt: org.created_at,
updatedAt: org.updated_at,
publicMetadata: org.public_metadata ?? {},
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Organization ID' },
name: { type: 'string', description: 'Organization name' },
slug: { type: 'string', description: 'Organization slug', optional: true },
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
membersCount: { type: 'number', description: 'Number of members', optional: true },
pendingInvitationsCount: {
type: 'number',
description: 'Number of pending invitations',
optional: true,
},
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
publicMetadata: { type: 'json', description: 'Public metadata' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,93 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkGetSessionParams,
ClerkGetSessionResponse,
ClerkSession,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkGetSession')
export const clerkGetSessionTool: ToolConfig<ClerkGetSessionParams, ClerkGetSessionResponse> = {
id: 'clerk_get_session',
name: 'Get Session from Clerk',
description: 'Retrieve a single session by ID from Clerk',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
sessionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the session to retrieve',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/sessions/${params.sessionId}`,
method: 'GET',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
},
transformResponse: async (response: Response) => {
const data: ClerkSession | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get session from Clerk'
)
}
const session = data as ClerkSession
return {
success: true,
output: {
id: session.id,
userId: session.user_id,
clientId: session.client_id,
status: session.status,
lastActiveAt: session.last_active_at ?? null,
lastActiveOrganizationId: session.last_active_organization_id ?? null,
expireAt: session.expire_at ?? null,
abandonAt: session.abandon_at ?? null,
createdAt: session.created_at,
updatedAt: session.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Session ID' },
userId: { type: 'string', description: 'User ID' },
clientId: { type: 'string', description: 'Client ID' },
status: { type: 'string', description: 'Session status' },
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
lastActiveOrganizationId: {
type: 'string',
description: 'Last active organization ID',
optional: true,
},
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,167 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkEmailAddress,
ClerkGetUserParams,
ClerkGetUserResponse,
ClerkPhoneNumber,
ClerkUser,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkGetUser')
export const clerkGetUserTool: ToolConfig<ClerkGetUserParams, ClerkGetUserResponse> = {
id: 'clerk_get_user',
name: 'Get User from Clerk',
description: 'Retrieve a single user by their ID from Clerk',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the user to retrieve',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
method: 'GET',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
},
transformResponse: async (response: Response) => {
const data: ClerkUser | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get user from Clerk'
)
}
const user = data as ClerkUser
return {
success: true,
output: {
id: user.id,
username: user.username ?? null,
firstName: user.first_name ?? null,
lastName: user.last_name ?? null,
imageUrl: user.image_url ?? null,
hasImage: user.has_image ?? false,
primaryEmailAddressId: user.primary_email_address_id ?? null,
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
primaryWeb3WalletId: user.primary_web3_wallet_id ?? null,
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
id: email.id,
emailAddress: email.email_address,
verified: email.verification?.status === 'verified',
})),
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
id: phone.id,
phoneNumber: phone.phone_number,
verified: phone.verification?.status === 'verified',
})),
externalId: user.external_id ?? null,
passwordEnabled: user.password_enabled ?? false,
twoFactorEnabled: user.two_factor_enabled ?? false,
totpEnabled: user.totp_enabled ?? false,
backupCodeEnabled: user.backup_code_enabled ?? false,
banned: user.banned ?? false,
locked: user.locked ?? false,
deleteSelfEnabled: user.delete_self_enabled ?? false,
createOrganizationEnabled: user.create_organization_enabled ?? false,
lastSignInAt: user.last_sign_in_at ?? null,
lastActiveAt: user.last_active_at ?? null,
createdAt: user.created_at,
updatedAt: user.updated_at,
publicMetadata: user.public_metadata ?? {},
privateMetadata: user.private_metadata ?? {},
unsafeMetadata: user.unsafe_metadata ?? {},
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username', optional: true },
firstName: { type: 'string', description: 'First name', optional: true },
lastName: { type: 'string', description: 'Last name', optional: true },
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
hasImage: { type: 'boolean', description: 'Whether user has a profile image' },
primaryEmailAddressId: {
type: 'string',
description: 'Primary email address ID',
optional: true,
},
primaryPhoneNumberId: {
type: 'string',
description: 'Primary phone number ID',
optional: true,
},
primaryWeb3WalletId: { type: 'string', description: 'Primary Web3 wallet ID', optional: true },
emailAddresses: {
type: 'array',
description: 'User email addresses',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Email address ID' },
emailAddress: { type: 'string', description: 'Email address' },
verified: { type: 'boolean', description: 'Whether email is verified' },
},
},
},
phoneNumbers: {
type: 'array',
description: 'User phone numbers',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Phone number ID' },
phoneNumber: { type: 'string', description: 'Phone number' },
verified: { type: 'boolean', description: 'Whether phone is verified' },
},
},
},
externalId: { type: 'string', description: 'External system ID', optional: true },
passwordEnabled: { type: 'boolean', description: 'Whether password is enabled' },
twoFactorEnabled: { type: 'boolean', description: 'Whether 2FA is enabled' },
totpEnabled: { type: 'boolean', description: 'Whether TOTP is enabled' },
backupCodeEnabled: { type: 'boolean', description: 'Whether backup codes are enabled' },
banned: { type: 'boolean', description: 'Whether user is banned' },
locked: { type: 'boolean', description: 'Whether user is locked' },
deleteSelfEnabled: { type: 'boolean', description: 'Whether user can delete themselves' },
createOrganizationEnabled: {
type: 'boolean',
description: 'Whether user can create organizations',
},
lastSignInAt: { type: 'number', description: 'Last sign-in timestamp', optional: true },
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
publicMetadata: { type: 'json', description: 'Public metadata (readable from frontend)' },
privateMetadata: { type: 'json', description: 'Private metadata (backend only)' },
unsafeMetadata: { type: 'json', description: 'Unsafe metadata (modifiable from frontend)' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,11 @@
export { clerkCreateOrganizationTool } from './create_organization'
export { clerkCreateUserTool } from './create_user'
export { clerkDeleteUserTool } from './delete_user'
export { clerkGetOrganizationTool } from './get_organization'
export { clerkGetSessionTool } from './get_session'
export { clerkGetUserTool } from './get_user'
export { clerkListOrganizationsTool } from './list_organizations'
export { clerkListSessionsTool } from './list_sessions'
export { clerkListUsersTool } from './list_users'
export { clerkRevokeSessionTool } from './revoke_session'
export { clerkUpdateUserTool } from './update_user'

View File

@@ -0,0 +1,157 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkListOrganizationsParams,
ClerkListOrganizationsResponse,
ClerkOrganization,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkListOrganizations')
export const clerkListOrganizationsTool: ToolConfig<
ClerkListOrganizationsParams,
ClerkListOrganizationsResponse
> = {
id: 'clerk_list_organizations',
name: 'List Organizations from Clerk',
description: 'List all organizations in your Clerk application with optional filtering',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination',
},
includeMembersCount: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include member count for each organization',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search by organization ID, name, or slug',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field (name, created_at, members_count) with +/- prefix',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.limit) queryParams.append('limit', params.limit.toString())
if (params.offset) queryParams.append('offset', params.offset.toString())
if (params.includeMembersCount) queryParams.append('include_members_count', 'true')
if (params.query) queryParams.append('query', params.query)
if (params.orderBy) queryParams.append('order_by', params.orderBy)
const queryString = queryParams.toString()
return queryString
? `https://api.clerk.com/v1/organizations?${queryString}`
: 'https://api.clerk.com/v1/organizations'
},
method: 'GET',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
},
transformResponse: async (response: Response) => {
const json: { data: ClerkOrganization[]; total_count: number } | ClerkApiError =
await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data: json, status: response.status })
throw new Error(
(json as ClerkApiError).errors?.[0]?.message || 'Failed to list organizations from Clerk'
)
}
const responseData = json as { data: ClerkOrganization[]; total_count: number }
// Transform each organization to extract key fields
const organizations = responseData.data.map((org) => ({
id: org.id,
name: org.name,
slug: org.slug ?? null,
imageUrl: org.image_url ?? null,
hasImage: org.has_image ?? false,
membersCount: org.members_count ?? null,
pendingInvitationsCount: org.pending_invitations_count ?? null,
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
adminDeleteEnabled: org.admin_delete_enabled ?? false,
createdBy: org.created_by ?? null,
createdAt: org.created_at,
updatedAt: org.updated_at,
publicMetadata: org.public_metadata ?? {},
}))
return {
success: true,
output: {
organizations,
totalCount: responseData.total_count ?? organizations.length,
success: true,
},
}
},
outputs: {
organizations: {
type: 'array',
description: 'Array of Clerk organization objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Organization ID' },
name: { type: 'string', description: 'Organization name' },
slug: { type: 'string', description: 'Organization slug', optional: true },
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
membersCount: { type: 'number', description: 'Number of members', optional: true },
pendingInvitationsCount: {
type: 'number',
description: 'Number of pending invitations',
optional: true,
},
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
publicMetadata: { type: 'json', description: 'Public metadata' },
},
},
},
totalCount: { type: 'number', description: 'Total number of organizations' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,152 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkListSessionsParams,
ClerkListSessionsResponse,
ClerkSession,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkListSessions')
export const clerkListSessionsTool: ToolConfig<ClerkListSessionsParams, ClerkListSessionsResponse> =
{
id: 'clerk_list_sessions',
name: 'List Sessions from Clerk',
description: 'List sessions for a user or client in your Clerk application',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User ID to list sessions for (required if clientId not provided)',
},
clientId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Client ID to list sessions for (required if userId not provided)',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Filter by session status (abandoned, active, ended, expired, pending, removed, replaced, revoked)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.userId) queryParams.append('user_id', params.userId)
if (params.clientId) queryParams.append('client_id', params.clientId)
if (params.status) queryParams.append('status', params.status)
if (params.limit) queryParams.append('limit', params.limit.toString())
if (params.offset) queryParams.append('offset', params.offset.toString())
const queryString = queryParams.toString()
return queryString
? `https://api.clerk.com/v1/sessions?${queryString}`
: 'https://api.clerk.com/v1/sessions'
},
method: 'GET',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
},
transformResponse: async (response: Response) => {
const data: ClerkSession[] | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to list sessions from Clerk'
)
}
const totalCount = Number.parseInt(response.headers.get('x-total-count') || '0', 10)
const sessions = (data as ClerkSession[]).map((session) => ({
id: session.id,
userId: session.user_id,
clientId: session.client_id,
status: session.status,
lastActiveAt: session.last_active_at ?? null,
lastActiveOrganizationId: session.last_active_organization_id ?? null,
expireAt: session.expire_at ?? null,
abandonAt: session.abandon_at ?? null,
createdAt: session.created_at,
updatedAt: session.updated_at,
}))
return {
success: true,
output: {
sessions,
totalCount: totalCount || sessions.length,
success: true,
},
}
},
outputs: {
sessions: {
type: 'array',
description: 'Array of Clerk session objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Session ID' },
userId: { type: 'string', description: 'User ID' },
clientId: { type: 'string', description: 'Client ID' },
status: { type: 'string', description: 'Session status' },
lastActiveAt: {
type: 'number',
description: 'Last activity timestamp',
optional: true,
},
lastActiveOrganizationId: {
type: 'string',
description: 'Last active organization ID',
optional: true,
},
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
},
},
},
totalCount: { type: 'number', description: 'Total number of sessions' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,250 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkEmailAddress,
ClerkListUsersParams,
ClerkListUsersResponse,
ClerkPhoneNumber,
ClerkUser,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkListUsers')
export const clerkListUsersTool: ToolConfig<ClerkListUsersParams, ClerkListUsersResponse> = {
id: 'clerk_list_users',
name: 'List Users from Clerk',
description: 'List all users in your Clerk application with optional filtering and pagination',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default: 10)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field with optional +/- prefix for direction (default: -created_at)',
},
emailAddress: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by email address (comma-separated for multiple)',
},
phoneNumber: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by phone number (comma-separated for multiple)',
},
externalId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by external ID (comma-separated for multiple)',
},
username: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by username (comma-separated for multiple)',
},
userId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by user ID (comma-separated for multiple)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search query to match across email, phone, username, and names',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.limit) queryParams.append('limit', params.limit.toString())
if (params.offset) queryParams.append('offset', params.offset.toString())
if (params.orderBy) queryParams.append('order_by', params.orderBy)
if (params.query) queryParams.append('query', params.query)
// Handle comma-separated array params
if (params.emailAddress) {
params.emailAddress.split(',').forEach((email) => {
queryParams.append('email_address', email.trim())
})
}
if (params.phoneNumber) {
params.phoneNumber.split(',').forEach((phone) => {
queryParams.append('phone_number', phone.trim())
})
}
if (params.externalId) {
params.externalId.split(',').forEach((id) => {
queryParams.append('external_id', id.trim())
})
}
if (params.username) {
params.username.split(',').forEach((uname) => {
queryParams.append('username', uname.trim())
})
}
if (params.userId) {
params.userId.split(',').forEach((id) => {
queryParams.append('user_id', id.trim())
})
}
const queryString = queryParams.toString()
return queryString
? `https://api.clerk.com/v1/users?${queryString}`
: 'https://api.clerk.com/v1/users'
},
method: 'GET',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
},
transformResponse: async (response: Response) => {
const data: ClerkUser[] | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to list users from Clerk'
)
}
// The response is an array of users, total_count is in the header
const totalCount = Number.parseInt(response.headers.get('x-total-count') || '0', 10)
// Transform each user to extract key fields
const users = (data as ClerkUser[]).map((user) => ({
id: user.id,
username: user.username ?? null,
firstName: user.first_name ?? null,
lastName: user.last_name ?? null,
imageUrl: user.image_url ?? null,
hasImage: user.has_image ?? false,
primaryEmailAddressId: user.primary_email_address_id ?? null,
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
id: email.id,
emailAddress: email.email_address,
})),
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
id: phone.id,
phoneNumber: phone.phone_number,
})),
externalId: user.external_id ?? null,
passwordEnabled: user.password_enabled ?? false,
twoFactorEnabled: user.two_factor_enabled ?? false,
banned: user.banned ?? false,
locked: user.locked ?? false,
lastSignInAt: user.last_sign_in_at ?? null,
lastActiveAt: user.last_active_at ?? null,
createdAt: user.created_at,
updatedAt: user.updated_at,
publicMetadata: user.public_metadata ?? {},
}))
return {
success: true,
output: {
users,
totalCount: totalCount || users.length,
success: true,
},
}
},
outputs: {
users: {
type: 'array',
description: 'Array of Clerk user objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'User ID' },
username: { type: 'string', description: 'Username', optional: true },
firstName: { type: 'string', description: 'First name', optional: true },
lastName: { type: 'string', description: 'Last name', optional: true },
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
hasImage: { type: 'boolean', description: 'Whether user has a profile image' },
primaryEmailAddressId: {
type: 'string',
description: 'Primary email address ID',
optional: true,
},
primaryPhoneNumberId: {
type: 'string',
description: 'Primary phone number ID',
optional: true,
},
emailAddresses: {
type: 'array',
description: 'User email addresses',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Email address ID' },
emailAddress: { type: 'string', description: 'Email address' },
},
},
},
phoneNumbers: {
type: 'array',
description: 'User phone numbers',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Phone number ID' },
phoneNumber: { type: 'string', description: 'Phone number' },
},
},
},
externalId: { type: 'string', description: 'External system ID', optional: true },
passwordEnabled: { type: 'boolean', description: 'Whether password is enabled' },
twoFactorEnabled: { type: 'boolean', description: 'Whether 2FA is enabled' },
banned: { type: 'boolean', description: 'Whether user is banned' },
locked: { type: 'boolean', description: 'Whether user is locked' },
lastSignInAt: { type: 'number', description: 'Last sign-in timestamp', optional: true },
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
publicMetadata: { type: 'json', description: 'Public metadata' },
},
},
},
totalCount: { type: 'number', description: 'Total number of users matching the query' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,96 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkRevokeSessionParams,
ClerkRevokeSessionResponse,
ClerkSession,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkRevokeSession')
export const clerkRevokeSessionTool: ToolConfig<
ClerkRevokeSessionParams,
ClerkRevokeSessionResponse
> = {
id: 'clerk_revoke_session',
name: 'Revoke Session in Clerk',
description: 'Revoke a session to immediately invalidate it',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
sessionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the session to revoke',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/sessions/${params.sessionId}/revoke`,
method: 'POST',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
},
transformResponse: async (response: Response) => {
const data: ClerkSession | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to revoke session in Clerk'
)
}
const session = data as ClerkSession
return {
success: true,
output: {
id: session.id,
userId: session.user_id,
clientId: session.client_id,
status: session.status,
lastActiveAt: session.last_active_at ?? null,
lastActiveOrganizationId: session.last_active_organization_id ?? null,
expireAt: session.expire_at ?? null,
abandonAt: session.abandon_at ?? null,
createdAt: session.created_at,
updatedAt: session.updated_at,
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Session ID' },
userId: { type: 'string', description: 'User ID' },
clientId: { type: 'string', description: 'Client ID' },
status: { type: 'string', description: 'Session status (should be revoked)' },
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
lastActiveOrganizationId: {
type: 'string',
description: 'Last active organization ID',
optional: true,
},
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,552 @@
import type { ToolResponse } from '@/tools/types'
/**
* Clerk API error response
*/
export interface ClerkApiError {
errors?: { message: string }[]
}
/**
* Clerk delete response
*/
export interface ClerkDeleteResponse {
id: string
object: string
deleted: boolean
}
/**
* Clerk User object
*/
export interface ClerkUser {
id: string
object: 'user'
username: string | null
first_name: string | null
last_name: string | null
image_url: string
has_image: boolean
primary_email_address_id: string | null
primary_phone_number_id: string | null
primary_web3_wallet_id: string | null
password_enabled: boolean
two_factor_enabled: boolean
totp_enabled: boolean
backup_code_enabled: boolean
email_addresses: ClerkEmailAddress[]
phone_numbers: ClerkPhoneNumber[]
web3_wallets: ClerkWeb3Wallet[]
external_accounts: ClerkExternalAccount[]
external_id: string | null
last_sign_in_at: number | null
banned: boolean
locked: boolean
lockout_expires_in_seconds: number | null
verification_attempts_remaining: number | null
created_at: number
updated_at: number
delete_self_enabled: boolean
create_organization_enabled: boolean
last_active_at: number | null
profile_image_url: string
public_metadata: Record<string, unknown>
private_metadata: Record<string, unknown>
unsafe_metadata: Record<string, unknown>
}
export interface ClerkEmailAddress {
id: string
object: 'email_address'
email_address: string
verification: ClerkVerification | null
linked_to: ClerkLinkedIdentifier[]
created_at: number
updated_at: number
}
export interface ClerkPhoneNumber {
id: string
object: 'phone_number'
phone_number: string
reserved_for_second_factor: boolean
default_second_factor: boolean
verification: ClerkVerification | null
linked_to: ClerkLinkedIdentifier[]
backup_codes: string[] | null
created_at: number
updated_at: number
}
export interface ClerkWeb3Wallet {
id: string
object: 'web3_wallet'
web3_wallet: string
verification: ClerkVerification | null
created_at: number
updated_at: number
}
export interface ClerkExternalAccount {
id: string
object: 'external_account'
provider: string
identification_id: string
provider_user_id: string
approved_scopes: string
email_address: string
first_name: string
last_name: string
image_url: string
username: string | null
public_metadata: Record<string, unknown>
label: string | null
verification: ClerkVerification | null
created_at: number
updated_at: number
}
export interface ClerkVerification {
status: string
strategy: string
attempts: number | null
expire_at: number | null
}
export interface ClerkLinkedIdentifier {
type: string
id: string
}
/**
* Clerk Organization object
*/
export interface ClerkOrganization {
id: string
object: 'organization'
name: string
slug: string
image_url: string
has_image: boolean
members_count?: number
pending_invitations_count?: number
max_allowed_memberships: number
admin_delete_enabled: boolean
public_metadata: Record<string, unknown>
private_metadata: Record<string, unknown>
created_by: string
created_at: number
updated_at: number
}
/**
* Clerk Session object
*/
export interface ClerkSession {
id: string
object: 'session'
user_id: string
client_id: string
actor: Record<string, unknown> | null
status:
| 'abandoned'
| 'active'
| 'ended'
| 'expired'
| 'pending'
| 'removed'
| 'replaced'
| 'revoked'
last_active_organization_id: string | null
last_active_at: number
expire_at: number
abandon_at: number
created_at: number
updated_at: number
}
/**
* Transformed email address for outputs
*/
export interface ClerkEmailAddressOutput {
id: string
emailAddress: string
verified?: boolean
}
/**
* Transformed phone number for outputs
*/
export interface ClerkPhoneNumberOutput {
id: string
phoneNumber: string
verified?: boolean
}
/**
* Transformed user for list outputs
*/
export interface ClerkUserOutput {
id: string
username: string | null
firstName: string | null
lastName: string | null
imageUrl: string | null
hasImage: boolean
primaryEmailAddressId: string | null
primaryPhoneNumberId: string | null
emailAddresses: ClerkEmailAddressOutput[]
phoneNumbers: ClerkPhoneNumberOutput[]
externalId: string | null
passwordEnabled: boolean
twoFactorEnabled: boolean
banned: boolean
locked: boolean
lastSignInAt: number | null
lastActiveAt: number | null
createdAt: number
updatedAt: number
publicMetadata: Record<string, unknown>
}
/**
* Transformed organization for outputs
*/
export interface ClerkOrganizationOutput {
id: string
name: string
slug: string | null
imageUrl: string | null
hasImage: boolean
membersCount: number | null
pendingInvitationsCount: number | null
maxAllowedMemberships: number
adminDeleteEnabled: boolean
createdBy: string | null
createdAt: number
updatedAt: number
publicMetadata: Record<string, unknown>
}
/**
* Transformed session for outputs
*/
export interface ClerkSessionOutput {
id: string
userId: string
clientId: string
status: string
lastActiveAt: number | null
lastActiveOrganizationId: string | null
expireAt: number | null
abandonAt: number | null
createdAt: number
updatedAt: number
}
// List Users
export interface ClerkListUsersParams {
secretKey: string
limit?: number
offset?: number
orderBy?: string
emailAddress?: string
phoneNumber?: string
externalId?: string
username?: string
userId?: string
query?: string
}
export interface ClerkListUsersResponse extends ToolResponse {
output: {
users: ClerkUserOutput[]
totalCount: number
success: boolean
}
}
// Get User
export interface ClerkGetUserParams {
secretKey: string
userId: string
}
export interface ClerkGetUserResponse extends ToolResponse {
output: {
id: string
username: string | null
firstName: string | null
lastName: string | null
imageUrl: string | null
hasImage: boolean
primaryEmailAddressId: string | null
primaryPhoneNumberId: string | null
primaryWeb3WalletId: string | null
emailAddresses: ClerkEmailAddressOutput[]
phoneNumbers: ClerkPhoneNumberOutput[]
externalId: string | null
passwordEnabled: boolean
twoFactorEnabled: boolean
totpEnabled: boolean
backupCodeEnabled: boolean
banned: boolean
locked: boolean
deleteSelfEnabled: boolean
createOrganizationEnabled: boolean
lastSignInAt: number | null
lastActiveAt: number | null
createdAt: number
updatedAt: number
publicMetadata: Record<string, unknown>
privateMetadata: Record<string, unknown>
unsafeMetadata: Record<string, unknown>
success: boolean
}
}
// Create User
export interface ClerkCreateUserParams {
secretKey: string
emailAddress?: string | string[]
phoneNumber?: string | string[]
username?: string
password?: string
firstName?: string
lastName?: string
externalId?: string
publicMetadata?: Record<string, unknown>
privateMetadata?: Record<string, unknown>
unsafeMetadata?: Record<string, unknown>
skipPasswordChecks?: boolean
skipPasswordRequirement?: boolean
}
export interface ClerkCreateUserResponse extends ToolResponse {
output: {
id: string
username: string | null
firstName: string | null
lastName: string | null
imageUrl: string | null
primaryEmailAddressId: string | null
primaryPhoneNumberId: string | null
emailAddresses: ClerkEmailAddressOutput[]
phoneNumbers: ClerkPhoneNumberOutput[]
externalId: string | null
createdAt: number
updatedAt: number
publicMetadata: Record<string, unknown>
success: boolean
}
}
// Update User
export interface ClerkUpdateUserParams {
secretKey: string
userId: string
firstName?: string
lastName?: string
username?: string
password?: string
externalId?: string
primaryEmailAddressId?: string
primaryPhoneNumberId?: string
publicMetadata?: Record<string, unknown>
privateMetadata?: Record<string, unknown>
unsafeMetadata?: Record<string, unknown>
skipPasswordChecks?: boolean
}
export interface ClerkUpdateUserResponse extends ToolResponse {
output: {
id: string
username: string | null
firstName: string | null
lastName: string | null
imageUrl: string | null
primaryEmailAddressId: string | null
primaryPhoneNumberId: string | null
emailAddresses: ClerkEmailAddressOutput[]
phoneNumbers: ClerkPhoneNumberOutput[]
externalId: string | null
banned: boolean
locked: boolean
createdAt: number
updatedAt: number
publicMetadata: Record<string, unknown>
success: boolean
}
}
// Delete User
export interface ClerkDeleteUserParams {
secretKey: string
userId: string
}
export interface ClerkDeleteUserResponse extends ToolResponse {
output: {
id: string
object: string
deleted: boolean
success: boolean
}
}
// List Organizations
export interface ClerkListOrganizationsParams {
secretKey: string
limit?: number
offset?: number
includeMembersCount?: boolean
query?: string
orderBy?: string
}
export interface ClerkListOrganizationsResponse extends ToolResponse {
output: {
organizations: ClerkOrganizationOutput[]
totalCount: number
success: boolean
}
}
// Get Organization
export interface ClerkGetOrganizationParams {
secretKey: string
organizationId: string
}
export interface ClerkGetOrganizationResponse extends ToolResponse {
output: {
id: string
name: string
slug: string | null
imageUrl: string | null
hasImage: boolean
membersCount: number | null
pendingInvitationsCount: number | null
maxAllowedMemberships: number
adminDeleteEnabled: boolean
createdBy: string | null
createdAt: number
updatedAt: number
publicMetadata: Record<string, unknown>
success: boolean
}
}
// Create Organization
export interface ClerkCreateOrganizationParams {
secretKey: string
name: string
createdBy: string
slug?: string
maxAllowedMemberships?: number
publicMetadata?: Record<string, unknown>
privateMetadata?: Record<string, unknown>
}
export interface ClerkCreateOrganizationResponse extends ToolResponse {
output: {
id: string
name: string
slug: string | null
imageUrl: string | null
hasImage: boolean
membersCount: number | null
pendingInvitationsCount: number | null
maxAllowedMemberships: number
adminDeleteEnabled: boolean
createdBy: string | null
createdAt: number
updatedAt: number
publicMetadata: Record<string, unknown>
success: boolean
}
}
// List Sessions
export interface ClerkListSessionsParams {
secretKey: string
userId?: string
clientId?: string
status?:
| 'abandoned'
| 'active'
| 'ended'
| 'expired'
| 'pending'
| 'removed'
| 'replaced'
| 'revoked'
limit?: number
offset?: number
}
export interface ClerkListSessionsResponse extends ToolResponse {
output: {
sessions: ClerkSessionOutput[]
totalCount: number
success: boolean
}
}
// Get Session
export interface ClerkGetSessionParams {
secretKey: string
sessionId: string
}
export interface ClerkGetSessionResponse extends ToolResponse {
output: {
id: string
userId: string
clientId: string
status: string
lastActiveAt: number | null
lastActiveOrganizationId: string | null
expireAt: number | null
abandonAt: number | null
createdAt: number
updatedAt: number
success: boolean
}
}
// Revoke Session
export interface ClerkRevokeSessionParams {
secretKey: string
sessionId: string
}
export interface ClerkRevokeSessionResponse extends ToolResponse {
output: {
id: string
userId: string
clientId: string
status: string
lastActiveAt: number | null
lastActiveOrganizationId: string | null
expireAt: number | null
abandonAt: number | null
createdAt: number
updatedAt: number
success: boolean
}
}
// Generic response type for the block
export type ClerkResponse =
| ClerkListUsersResponse
| ClerkGetUserResponse
| ClerkCreateUserResponse
| ClerkUpdateUserResponse
| ClerkDeleteUserResponse
| ClerkListOrganizationsResponse
| ClerkGetOrganizationResponse
| ClerkCreateOrganizationResponse
| ClerkListSessionsResponse
| ClerkGetSessionResponse
| ClerkRevokeSessionResponse

View File

@@ -0,0 +1,225 @@
import { createLogger } from '@sim/logger'
import type {
ClerkApiError,
ClerkEmailAddress,
ClerkPhoneNumber,
ClerkUpdateUserParams,
ClerkUpdateUserResponse,
ClerkUser,
} from '@/tools/clerk/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ClerkUpdateUser')
export const clerkUpdateUserTool: ToolConfig<ClerkUpdateUserParams, ClerkUpdateUserResponse> = {
id: 'clerk_update_user',
name: 'Update User in Clerk',
description: 'Update an existing user in your Clerk application',
version: '1.0.0',
params: {
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Clerk Secret Key for API authentication',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the user to update',
},
firstName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'First name of the user',
},
lastName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Last name of the user',
},
username: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Username (must be unique)',
},
password: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New password (minimum 8 characters)',
},
externalId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'External system identifier',
},
primaryEmailAddressId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ID of verified email to set as primary',
},
primaryPhoneNumberId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ID of verified phone to set as primary',
},
publicMetadata: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Public metadata (JSON object)',
},
privateMetadata: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Private metadata (JSON object)',
},
unsafeMetadata: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Unsafe metadata (JSON object)',
},
skipPasswordChecks: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Skip password validation checks',
},
},
request: {
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
method: 'PATCH',
headers: (params) => {
if (!params.secretKey) {
throw new Error('Clerk Secret Key is required')
}
return {
Authorization: `Bearer ${params.secretKey}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
const body: Record<string, unknown> = {}
if (params.firstName !== undefined) body.first_name = params.firstName?.trim()
if (params.lastName !== undefined) body.last_name = params.lastName?.trim()
if (params.username !== undefined) body.username = params.username?.trim()
if (params.password !== undefined) body.password = params.password
if (params.externalId !== undefined) body.external_id = params.externalId?.trim()
if (params.primaryEmailAddressId !== undefined)
body.primary_email_address_id = params.primaryEmailAddressId?.trim()
if (params.primaryPhoneNumberId !== undefined)
body.primary_phone_number_id = params.primaryPhoneNumberId?.trim()
if (params.publicMetadata !== undefined) body.public_metadata = params.publicMetadata
if (params.privateMetadata !== undefined) body.private_metadata = params.privateMetadata
if (params.unsafeMetadata !== undefined) body.unsafe_metadata = params.unsafeMetadata
if (params.skipPasswordChecks !== undefined)
body.skip_password_checks = params.skipPasswordChecks
return body
},
},
transformResponse: async (response: Response) => {
const data: ClerkUser | ClerkApiError = await response.json()
if (!response.ok) {
logger.error('Clerk API request failed', { data, status: response.status })
throw new Error(
(data as ClerkApiError).errors?.[0]?.message || 'Failed to update user in Clerk'
)
}
const user = data as ClerkUser
return {
success: true,
output: {
id: user.id,
username: user.username ?? null,
firstName: user.first_name ?? null,
lastName: user.last_name ?? null,
imageUrl: user.image_url ?? null,
primaryEmailAddressId: user.primary_email_address_id ?? null,
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
id: email.id,
emailAddress: email.email_address,
verified: email.verification?.status === 'verified',
})),
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
id: phone.id,
phoneNumber: phone.phone_number,
verified: phone.verification?.status === 'verified',
})),
externalId: user.external_id ?? null,
banned: user.banned ?? false,
locked: user.locked ?? false,
createdAt: user.created_at,
updatedAt: user.updated_at,
publicMetadata: user.public_metadata ?? {},
success: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Updated user ID' },
username: { type: 'string', description: 'Username', optional: true },
firstName: { type: 'string', description: 'First name', optional: true },
lastName: { type: 'string', description: 'Last name', optional: true },
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
primaryEmailAddressId: {
type: 'string',
description: 'Primary email address ID',
optional: true,
},
primaryPhoneNumberId: {
type: 'string',
description: 'Primary phone number ID',
optional: true,
},
emailAddresses: {
type: 'array',
description: 'User email addresses',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Email address ID' },
emailAddress: { type: 'string', description: 'Email address' },
verified: { type: 'boolean', description: 'Whether email is verified' },
},
},
},
phoneNumbers: {
type: 'array',
description: 'User phone numbers',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Phone number ID' },
phoneNumber: { type: 'string', description: 'Phone number' },
verified: { type: 'boolean', description: 'Whether phone is verified' },
},
},
},
externalId: { type: 'string', description: 'External system ID', optional: true },
banned: { type: 'boolean', description: 'Whether user is banned' },
locked: { type: 'boolean', description: 'Whether user is locked' },
createdAt: { type: 'number', description: 'Creation timestamp' },
updatedAt: { type: 'number', description: 'Last update timestamp' },
publicMetadata: { type: 'json', description: 'Public metadata' },
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -75,6 +75,19 @@ import {
calendlyListWebhooksTool,
} from '@/tools/calendly'
import { clayPopulateTool } from '@/tools/clay'
import {
clerkCreateOrganizationTool,
clerkCreateUserTool,
clerkDeleteUserTool,
clerkGetOrganizationTool,
clerkGetSessionTool,
clerkGetUserTool,
clerkListOrganizationsTool,
clerkListSessionsTool,
clerkListUsersTool,
clerkRevokeSessionTool,
clerkUpdateUserTool,
} from '@/tools/clerk'
import {
confluenceCreateCommentTool,
confluenceCreatePageTool,
@@ -2571,6 +2584,17 @@ export const tools: Record<string, ToolConfig> = {
telegram_send_video: telegramSendVideoTool,
telegram_send_document: telegramSendDocumentTool,
clay_populate: clayPopulateTool,
clerk_list_users: clerkListUsersTool,
clerk_get_user: clerkGetUserTool,
clerk_create_user: clerkCreateUserTool,
clerk_update_user: clerkUpdateUserTool,
clerk_delete_user: clerkDeleteUserTool,
clerk_list_organizations: clerkListOrganizationsTool,
clerk_get_organization: clerkGetOrganizationTool,
clerk_create_organization: clerkCreateOrganizationTool,
clerk_list_sessions: clerkListSessionsTool,
clerk_get_session: clerkGetSessionTool,
clerk_revoke_session: clerkRevokeSessionTool,
discord_send_message: discordSendMessageTool,
discord_get_messages: discordGetMessagesTool,
discord_get_server: discordGetServerTool,