Skip to main content
Glama
extended.ts6.98 kB
import { z } from 'zod'; import { CannyClient } from '../client/canny.js'; import { validateToolInput } from '../utils/validation.js'; const GetCategoriesSchema = z.object({ boardId: z.string().min(1, 'Board ID is required'), }); const GetCommentsSchema = z.object({ postId: z.string().min(1, 'Post ID is required'), limit: z.number().min(1).max(50).optional().default(10), skip: z.number().min(0).optional().default(0), }); const GetUsersSchema = z.object({ limit: z.number().min(1).max(50).optional().default(10), skip: z.number().min(0).optional().default(0), search: z.string().optional(), }); const GetTagsSchema = z.object({ boardId: z.string().optional(), limit: z.number().min(1).max(50).optional().default(20), }); type GetCategoriesInput = z.infer<typeof GetCategoriesSchema>; type GetCommentsInput = z.infer<typeof GetCommentsSchema>; type GetUsersInput = z.infer<typeof GetUsersSchema>; type GetTagsInput = z.infer<typeof GetTagsSchema>; /** * Tool to get categories from a specific board */ export const getCategoresTool = { name: 'get_categories', description: 'Get all categories from a specific Canny board', inputSchema: { type: 'object', properties: { boardId: { type: 'string', description: 'ID of the board to get categories from' }, }, required: ['boardId'], additionalProperties: false, }, handler: async (args: unknown, client: CannyClient) => { const { boardId } = validateToolInput<GetCategoriesInput>(args, GetCategoriesSchema); const response = await client.getCategories(boardId); if (response.error) { throw new Error(`Failed to fetch categories: ${response.error}`); } if (!response.data || response.data.length === 0) { return `No categories found for board ${boardId}.`; } const categories = response.data.map(category => ({ id: category.id, name: category.name, postCount: category.postCount || 0, })); return `Found ${categories.length} category(ies) in board:\n\n${categories .map(category => `**${category.name}** (ID: ${category.id})\n` + ` Posts: ${category.postCount}\n` ) .join('\n')}`; }, }; /** * Tool to get comments from a specific post */ export const getCommentsTool = { name: 'get_comments', description: 'Get comments from a specific Canny post', inputSchema: { type: 'object', properties: { postId: { type: 'string', description: 'ID of the post to get comments from' }, limit: { type: 'number', minimum: 1, maximum: 50, default: 10, description: 'Number of comments to retrieve' }, skip: { type: 'number', minimum: 0, default: 0, description: 'Number of comments to skip for pagination' }, }, required: ['postId'], additionalProperties: false, }, handler: async (args: unknown, client: CannyClient) => { const { postId, limit, skip } = validateToolInput<GetCommentsInput>(args, GetCommentsSchema); const response = await client.getComments(postId, { limit, skip }); if (response.error) { throw new Error(`Failed to fetch comments: ${response.error}`); } if (!response.data?.comments || response.data.comments.length === 0) { return `No comments found for post ${postId}.`; } const comments = response.data.comments.map(comment => ({ id: comment.id, author: comment.author.name, value: comment.value?.substring(0, 200) + (comment.value && comment.value.length > 200 ? '...' : ''), created: new Date(comment.created).toLocaleDateString(), isInternal: comment.internal || false, })); return `Found ${comments.length} comment(s) for post ${postId}:\n\n${comments .map((comment, index) => `${index + 1}. **${comment.author}** (${comment.created})${comment.isInternal ? ' [INTERNAL]' : ''}\n` + ` "${comment.value}"\n` ) .join('\n')}${response.data.hasMore ? '\n(More comments available - increase limit or skip to see more)' : ''}`; }, }; /** * Tool to get users/authors from Canny */ export const getUsersTool = { name: 'get_users', description: 'Get users/authors from your Canny instance', inputSchema: { type: 'object', properties: { limit: { type: 'number', minimum: 1, maximum: 50, default: 10, description: 'Number of users to retrieve' }, skip: { type: 'number', minimum: 0, default: 0, description: 'Number of users to skip for pagination' }, search: { type: 'string', description: 'Search term to filter users by name or email' }, }, additionalProperties: false, }, handler: async (args: unknown, client: CannyClient) => { const { limit, skip, search } = validateToolInput<GetUsersInput>(args, GetUsersSchema); const response = await client.getUsers({ limit, skip, search }); if (response.error) { throw new Error(`Failed to fetch users: ${response.error}`); } if (!response.data?.users || response.data.users.length === 0) { return 'No users found matching the criteria.'; } const users = response.data.users.map(user => ({ id: user.id, name: user.name, email: user.email || 'No email', isAdmin: user.isAdmin || false, created: new Date(user.created).toLocaleDateString(), })); return `Found ${users.length} user(s):\n\n${users .map((user, index) => `${index + 1}. **${user.name}**${user.isAdmin ? ' [ADMIN]' : ''}\n` + ` Email: ${user.email}\n` + ` Joined: ${user.created}\n` ) .join('\n')}${response.data.hasMore ? '\n(More users available - increase limit or skip to see more)' : ''}`; }, }; /** * Tool to get tags from boards */ export const getTagsTool = { name: 'get_tags', description: 'Get all tags from Canny boards (optionally filtered by board)', inputSchema: { type: 'object', properties: { boardId: { type: 'string', description: 'Optional: ID of specific board to get tags from' }, limit: { type: 'number', minimum: 1, maximum: 50, default: 20, description: 'Number of tags to retrieve' }, }, additionalProperties: false, }, handler: async (args: unknown, client: CannyClient) => { const { boardId, limit } = validateToolInput<GetTagsInput>(args, GetTagsSchema); const response = await client.getTags({ boardId, limit }); if (response.error) { throw new Error(`Failed to fetch tags: ${response.error}`); } if (!response.data?.tags || response.data.tags.length === 0) { return boardId ? `No tags found for board ${boardId}.` : 'No tags found.'; } const tags = response.data.tags.map(tag => ({ id: tag.id, name: tag.name, postCount: tag.postCount || 0, })); return `Found ${tags.length} tag(s):\n\n${tags .map(tag => `**${tag.name}** (ID: ${tag.id})\n` + ` Posts: ${tag.postCount}\n` ) .join('\n')}`; }, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/itsocialist/canny-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server