Skip to main content
Glama
index.ts10.7 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from '@modelcontextprotocol/sdk/types.js'; import { z } from 'zod'; import { searchPackages, getPackageDetails, getCategories, getFeaturedPackages, type TypstPackage, type PackageDetails, } from './scraper.js'; // Define the tools available in this MCP server const TOOLS: Tool[] = [ { name: 'search_packages', description: 'Search for Typst packages in the Typst Universe. You can search by query text, filter by category, and specify the kind (packages or templates).', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query text to find packages (e.g., "math", "diagram", "table")', }, kind: { type: 'string', enum: ['packages', 'templates'], description: 'Type of items to search for. Defaults to "packages".', default: 'packages', }, category: { type: 'string', description: 'Filter by category (e.g., "visualization", "math", "text")', }, limit: { type: 'number', description: 'Maximum number of results to return. Defaults to 20.', default: 20, }, }, }, }, { name: 'get_package_details', description: 'Get detailed information about a specific Typst package, including its description, authors, categories, repository link, import code, and version history.', inputSchema: { type: 'object', properties: { packageName: { type: 'string', description: 'The exact name of the package (e.g., "cetz", "polylux", "fletcher")', }, }, required: ['packageName'], }, }, { name: 'list_categories', description: 'List all available package categories in Typst Universe.', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_featured_packages', description: 'Get a list of featured/popular packages from Typst Universe.', inputSchema: { type: 'object', properties: {}, }, }, ]; // Zod schemas for validation const SearchPackagesSchema = z.object({ query: z.string().optional(), kind: z.enum(['packages', 'templates']).optional().default('packages'), category: z.string().optional(), limit: z.number().optional().default(20), }); const GetPackageDetailsSchema = z.object({ packageName: z.string().min(1, 'Package name is required'), }); /** * Formats a package for display */ function formatPackage(pkg: TypstPackage): string { return `📦 ${pkg.name} v${pkg.version}\n ${pkg.description}\n URL: ${pkg.url}`; } /** * Formats package details for display */ function formatPackageDetails(details: PackageDetails): string { const lines = [ `📦 ${details.name} v${details.version}`, ``, `📝 Description: ${details.description}`, ``, `📥 Import: ${details.importCode}`, ``, ]; if (details.authors.length > 0) { lines.push(`👤 Authors: ${details.authors.join(', ')}`); } if (details.categories.length > 0) { lines.push(`🏷️ Categories: ${details.categories.join(', ')}`); } if (details.repository) { lines.push(`📂 Repository: ${details.repository}`); } if (details.homepage) { lines.push(`🌐 Homepage: ${details.homepage}`); } lines.push(`🔗 URL: ${details.url}`); if (details.versionHistory.length > 0) { lines.push(``, `📜 Recent Versions: ${details.versionHistory.slice(0, 5).join(', ')}`); } return lines.join('\n'); } /** * Main server class */ class TypstUniverseServer { private server: Server; constructor() { this.server = new Server( { name: 'typst-universe-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.setupHandlers(); } private setupHandlers(): void { // Handle list tools request this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: TOOLS }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'search_packages': { const validatedArgs = SearchPackagesSchema.parse(args); const packages = await searchPackages({ query: validatedArgs.query, kind: validatedArgs.kind, category: validatedArgs.category, limit: validatedArgs.limit, }); if (packages.length === 0) { return { content: [ { type: 'text', text: 'No packages found matching your search criteria.', }, ], }; } const formattedResults = packages.map(formatPackage).join('\n\n'); return { content: [ { type: 'text', text: `Found ${packages.length} package(s):\n\n${formattedResults}`, }, ], }; } case 'get_package_details': { const validatedArgs = GetPackageDetailsSchema.parse(args); const details = await getPackageDetails(validatedArgs.packageName); if (!details) { return { content: [ { type: 'text', text: `Package "${validatedArgs.packageName}" not found. Please check the package name and try again.`, }, ], isError: true, }; } return { content: [ { type: 'text', text: formatPackageDetails(details), }, ], }; } case 'list_categories': { const categories = await getCategories(); if (categories.length === 0) { return { content: [ { type: 'text', text: 'No categories found.', }, ], }; } return { content: [ { type: 'text', text: `Available categories (${categories.length}):\n\n${categories.map(c => `• ${c}`).join('\n')}`, }, ], }; } case 'get_featured_packages': { const packages = await getFeaturedPackages(); if (packages.length === 0) { return { content: [ { type: 'text', text: 'No featured packages found.', }, ], }; } const formattedResults = packages.map(formatPackage).join('\n\n'); return { content: [ { type: 'text', text: `Featured packages (${packages.length}):\n\n${formattedResults}`, }, ], }; } default: return { content: [ { type: 'text', text: `Unknown tool: ${name}`, }, ], isError: true, }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Error executing tool "${name}": ${errorMessage}`, }, ], isError: true, }; } }); } async run(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Typst Universe MCP server running on stdio'); } } // Start the server const server = new TypstUniverseServer(); server.run().catch(console.error);

Implementation Reference

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/W1seGit/Typst-Universe-MCP'

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