Skip to main content
Glama
skill-parser.test.ts7.59 kB
/** * Tests for SKILL.md parser */ import { describe, it, expect } from 'vitest'; import { parseSkillMd, parseSkillMdOrThrow, extractPluginSlug, getAllowedTools, SkillParseError, } from './skill-parser.js'; describe('SKILL.md Parser', () => { describe('parseSkillMd', () => { it('should parse valid SKILL.md with all fields', () => { const content = `--- name: elementor-cookbook description: Best practices for editing Elementor pages via WP Navigator MCP. allowed-tools: "wpnav_get_page,wpnav_update_page,wpnav_snapshot_page" version: "1.0.0" min-plugin-version: "3.20.0" max-plugin-version: "4.0.0" requires-wpnav-pro: "1.9.0" --- # Elementor Cookbook ## Overview This is the body content. `; const result = parseSkillMd(content); expect(result.success).toBe(true); expect(result.cookbook).toBeDefined(); expect(result.cookbook!.frontmatter.name).toBe('elementor-cookbook'); expect(result.cookbook!.frontmatter.description).toBe( 'Best practices for editing Elementor pages via WP Navigator MCP.' ); expect(result.cookbook!.frontmatter['allowed-tools']).toBe( 'wpnav_get_page,wpnav_update_page,wpnav_snapshot_page' ); expect(result.cookbook!.frontmatter.version).toBe('1.0.0'); expect(result.cookbook!.frontmatter['min-plugin-version']).toBe('3.20.0'); expect(result.cookbook!.frontmatter['max-plugin-version']).toBe('4.0.0'); expect(result.cookbook!.frontmatter['requires-wpnav-pro']).toBe('1.9.0'); expect(result.cookbook!.body).toContain('# Elementor Cookbook'); expect(result.cookbook!.body).toContain('This is the body content.'); }); it('should parse minimal SKILL.md with only required fields', () => { const content = `--- name: gutenberg-cookbook description: Core WordPress block editor guidance. --- # Gutenberg Cookbook `; const result = parseSkillMd(content); expect(result.success).toBe(true); expect(result.cookbook!.frontmatter.name).toBe('gutenberg-cookbook'); expect(result.cookbook!.frontmatter.description).toBe( 'Core WordPress block editor guidance.' ); expect(result.cookbook!.frontmatter.version).toBeUndefined(); expect(result.cookbook!.body).toContain('# Gutenberg Cookbook'); }); it('should fail when frontmatter delimiters missing', () => { const content = `name: test-cookbook description: Test `; const result = parseSkillMd(content); expect(result.success).toBe(false); expect(result.error).toContain('must start with ---'); }); it('should fail when closing delimiter missing', () => { const content = `--- name: test-cookbook description: Test # Body without closing delimiter `; const result = parseSkillMd(content); expect(result.success).toBe(false); expect(result.error).toContain('Missing closing ---'); }); it('should fail when name is missing', () => { const content = `--- description: Test description --- # Body `; const result = parseSkillMd(content); expect(result.success).toBe(false); expect(result.error).toContain('name'); }); it('should fail when description is missing', () => { const content = `--- name: test-cookbook --- # Body `; const result = parseSkillMd(content); expect(result.success).toBe(false); expect(result.error).toContain('description'); }); it('should fail when name format is invalid', () => { const content = `--- name: Invalid Name With Spaces description: Test description --- # Body `; const result = parseSkillMd(content); expect(result.success).toBe(false); expect(result.error).toContain('lowercase'); }); it('should fail when name exceeds max length', () => { const longName = 'a'.repeat(65) + '-cookbook'; const content = `--- name: ${longName} description: Test description --- # Body `; const result = parseSkillMd(content); expect(result.success).toBe(false); expect(result.error).toContain('64 characters'); }); it('should fail when version format is invalid', () => { const content = `--- name: test-cookbook description: Test description version: "v1.0.0" --- # Body `; const result = parseSkillMd(content); expect(result.success).toBe(false); expect(result.error).toContain('semver'); }); it('should handle empty body', () => { const content = `--- name: test-cookbook description: Test description --- `; const result = parseSkillMd(content); expect(result.success).toBe(true); expect(result.cookbook!.body).toBe(''); }); it('should preserve raw frontmatter', () => { const content = `--- name: test-cookbook description: Test description --- # Body `; const result = parseSkillMd(content); expect(result.success).toBe(true); expect(result.cookbook!.rawFrontmatter).toContain('name: test-cookbook'); expect(result.cookbook!.rawFrontmatter).toContain('description: Test description'); }); }); describe('parseSkillMdOrThrow', () => { it('should return cookbook on success', () => { const content = `--- name: test-cookbook description: Test description --- # Body `; const cookbook = parseSkillMdOrThrow(content); expect(cookbook.frontmatter.name).toBe('test-cookbook'); }); it('should throw SkillParseError on failure', () => { const content = `invalid content`; expect(() => parseSkillMdOrThrow(content)).toThrow(SkillParseError); }); }); describe('extractPluginSlug', () => { it('should extract slug from name with -cookbook suffix', () => { expect(extractPluginSlug('elementor-cookbook')).toBe('elementor'); expect(extractPluginSlug('gutenberg-cookbook')).toBe('gutenberg'); expect(extractPluginSlug('woocommerce-cookbook')).toBe('woocommerce'); }); it('should return name unchanged if no -cookbook suffix', () => { expect(extractPluginSlug('elementor')).toBe('elementor'); expect(extractPluginSlug('woocommerce')).toBe('woocommerce'); }); }); describe('getAllowedTools', () => { it('should parse comma-separated tools', () => { const content = `--- name: test-cookbook description: Test allowed-tools: "wpnav_get_page, wpnav_update_page, wpnav_list_pages" --- `; const result = parseSkillMd(content); const tools = getAllowedTools(result.cookbook!); expect(tools).toEqual(['wpnav_get_page', 'wpnav_update_page', 'wpnav_list_pages']); }); it('should return empty array when allowed-tools not set', () => { const content = `--- name: test-cookbook description: Test --- `; const result = parseSkillMd(content); const tools = getAllowedTools(result.cookbook!); expect(tools).toEqual([]); }); it('should filter empty entries', () => { const content = `--- name: test-cookbook description: Test allowed-tools: "wpnav_get_page,, ,wpnav_update_page" --- `; const result = parseSkillMd(content); const tools = getAllowedTools(result.cookbook!); expect(tools).toEqual(['wpnav_get_page', 'wpnav_update_page']); }); }); }); describe('SKILL.md Loader Integration', () => { // These tests verify the loader correctly handles SKILL.md files // via the loadCookbook function it('should be tested via cookbook.test.ts integration tests', () => { // Placeholder - actual integration tests in cookbook.test.ts expect(true).toBe(true); }); });

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/littlebearapps/wp-navigator-mcp'

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