Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
skills-positional-args.test.ts7.21 kB
/** * Test that skills methods work with both keyword and positional arguments * * In MCP protocol and Claude Code execution: * - Keyword: await skills.find({ query: "pdf", depth: 2 }) * - Positional: await skills.find("pdf", 2) * * Both should work thanks to the inputSchema parameter ordering */ import { SkillsManagementMCP } from '../../src/internal-mcps/skills.js'; import * as path from 'path'; import * as os from 'os'; import * as fs from 'fs/promises'; import { setOverrideWorkingDirectory, resetPathsCache } from '../../src/utils/ncp-paths.js'; describe('Skills Methods - Positional Arguments Support', () => { let skillsMCP: SkillsManagementMCP; let testWorkingDir: string; let stubbedSkills: any[]; beforeEach(async () => { testWorkingDir = path.join(os.tmpdir(), `ncp-skills-mcp-${Date.now()}`); await fs.mkdir(testWorkingDir, { recursive: true }); stubbedSkills = []; resetPathsCache(); setOverrideWorkingDirectory(testWorkingDir); skillsMCP = new SkillsManagementMCP(); // Stub heavy dependencies so tests don't hit real filesystem/network (skillsMCP as any).skillsManager = { initialize: async () => {}, loadAllSkills: async () => stubbedSkills, getLoadedSkills: () => stubbedSkills }; (skillsMCP as any).marketplaceClient = { initialize: async () => {}, install: async () => ({ success: false, message: 'Skill not found' }), remove: async () => ({ success: true, message: 'Removed skill' }) }; }); afterEach(async () => { resetPathsCache(); setOverrideWorkingDirectory(null); await fs.rm(testWorkingDir, { recursive: true, force: true }); }); describe('skills:find parameter handling', () => { it('should accept keyword arguments', async () => { // Keyword arguments format const result = await skillsMCP.executeTool('find', { query: 'canvas', depth: 1 }); expect(result.success).toBe(true); // Should not error when using keyword arguments expect(result.content).toBeDefined(); }); it('should support empty call (list all)', async () => { // No arguments - should list all const result = await skillsMCP.executeTool('find', {}); expect(result.success).toBe(true); expect(result.content).toBeDefined(); }); it('should handle missing optional parameters', async () => { // Only query, other params should use defaults const result = await skillsMCP.executeTool('find', { query: 'design' }); expect(result.success).toBe(true); // depth should default to 1, page to 1, limit to 10 expect(result.content).toBeDefined(); }); it('should apply defaults correctly', async () => { // Empty params should use all defaults const result = await skillsMCP.executeTool('find', {}); expect(result.success).toBe(true); // Should work with all defaults: query='', depth=1, page=1, limit=10 expect(result.content).toBeDefined(); }); it('should respect pagination parameters', async () => { const result = await skillsMCP.executeTool('find', { page: 2, limit: 5 }); expect(result.success).toBe(true); expect(result.content).toBeDefined(); }); }); describe('skills:add parameter handling', () => { it('should handle required parameter', async () => { const result = await skillsMCP.executeTool('add', { skill_name: 'nonexistent-skill' }); // Will fail because skill doesn't exist, but parameter parsing should work expect(result.content).toBeDefined(); // Should fail with "not found" message, not "missing parameter" message expect(result.content).not.toContain('Missing required'); }); it('should require skill_name parameter', async () => { const result = await skillsMCP.executeTool('add', {}); expect(result.success).toBe(false); expect(result.content).toContain('Missing required'); }); }); describe('skills:remove parameter handling', () => { it('should handle required parameter', async () => { const result = await skillsMCP.executeTool('remove', { skill_name: 'nonexistent' }); expect(result.content).toBeDefined(); }); it('should require skill_name parameter', async () => { const result = await skillsMCP.executeTool('remove', {}); expect(result.success).toBe(false); expect(result.content).toContain('Missing required'); }); }); describe('skills:read_resource parameter handling', () => { it('should handle required parameters', async () => { const result = await skillsMCP.executeTool('read_resource', { skill_name: 'nonexistent', file_path: 'resources/test.md' }); expect(result.content).toBeDefined(); // Should fail with "skill not found", not "missing parameter" expect(result.content).not.toContain('Missing required'); }); it('should require both parameters', async () => { const result1 = await skillsMCP.executeTool('read_resource', { skill_name: 'test' }); expect(result1.success).toBe(false); expect(result1.content).toContain('Missing required'); const result2 = await skillsMCP.executeTool('read_resource', { file_path: 'test.md' }); expect(result2.success).toBe(false); expect(result2.content).toContain('Missing required'); }); }); describe('skills:list parameter handling', () => { it('should work with no parameters', async () => { // skills:list should work with empty params const result = await skillsMCP.executeTool('list', {}); expect(result.success).toBe(true); expect(result.content).toBeDefined(); }); it('should work with undefined params', async () => { const result = await skillsMCP.executeTool('list', undefined); // Should handle undefined gracefully expect(result.content).toBeDefined(); }); }); describe('Positional argument mapping', () => { it('explains how MCP maps positional to named arguments', () => { // When Claude Code calls: await skills.find("pdf", 2) // The MCP SDK uses the inputSchema parameter order: // 1st positional "pdf" → query parameter // 2nd positional 2 → depth parameter // 3rd positional (if provided) → page parameter // 4th positional (if provided) → limit parameter // The executeTool receives: { query: "pdf", depth: 2 } // So positional arguments ARE supported through MCP protocol! const schema = { type: 'object', properties: { query: { type: 'string' }, depth: { type: 'number' }, page: { type: 'number' }, limit: { type: 'number' } } }; // MCP protocol automatically maps positional args to schema parameter order expect(schema.properties).toHaveProperty('query'); expect(schema.properties).toHaveProperty('depth'); expect(schema.properties).toHaveProperty('page'); expect(schema.properties).toHaveProperty('limit'); }); }); });

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/portel-dev/ncp'

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