Skip to main content
Glama
server.test.ts•12.3 kB
import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { join } from 'path'; describe('MCP Server Tests - ServiceNow Background Script Execution', () => { let client: Client; beforeAll(async () => { // Set up environment variables for ServiceNow (using placeholder values for tests) process.env.SERVICENOW_ACE_INSTANCE = 'test-instance.service-now.com'; process.env.SERVICENOW_ACE_USERNAME = 'test_user'; process.env.SERVICENOW_ACE_PASSWORD = 'test_password'; // Create MCP client using the official SDK approach const serverPath = join(__dirname, '../../build/index.js'); const transport = new StdioClientTransport({ command: 'node', args: [serverPath], env: { NODE_ENV: 'test', SERVICENOW_ACE_INSTANCE: 'test-instance.service-now.com', SERVICENOW_ACE_USERNAME: 'test_user', SERVICENOW_ACE_PASSWORD: 'test_password', }, }); client = new Client({ name: 'test-client', version: '1.1.0', }); await client.connect(transport); }, 10000); // 10 second timeout for server startup afterAll(async () => { if (client) { await client.close(); } }); describe('Server Connection', () => { it('should connect to server successfully', () => { expect(client).toBeDefined(); }); }); describe('Tool Discovery', () => { it('should list available tools', async () => { const tools = await client.listTools(); expect(tools).toHaveProperty('tools'); expect(Array.isArray(tools.tools)).toBe(true); expect(tools.tools.length).toBeGreaterThan(0); }); it('should expose execute_background_script tool', async () => { const tools = await client.listTools(); const scriptTool = tools.tools.find((tool: any) => tool.name === 'execute_background_script'); expect(scriptTool).toBeDefined(); expect(scriptTool?.name).toBe('execute_background_script'); expect(scriptTool?.description).toContain('ServiceNow'); expect(scriptTool?.description).toContain('SANDBOX ONLY'); expect(scriptTool?.inputSchema).toBeDefined(); expect(scriptTool?.inputSchema?.properties?.script).toBeDefined(); expect(scriptTool?.inputSchema?.properties?.scope).toBeDefined(); }); it('should have correct tool schema', async () => { const tools = await client.listTools(); const scriptTool = tools.tools.find((tool: any) => tool.name === 'execute_background_script'); expect((scriptTool as any)?.inputSchema?.required).toContain('script'); expect((scriptTool as any)?.inputSchema?.required).toContain('scope'); expect((scriptTool as any)?.inputSchema?.properties?.script?.type).toBe('string'); expect((scriptTool as any)?.inputSchema?.properties?.scope?.type).toBe('string'); expect((scriptTool as any)?.inputSchema?.properties?.timeout_ms?.type).toBe('number'); }); }); describe('Tool Execution - Error Handling', () => { it('should handle missing script parameter', async () => { const result = await client.callTool({ name: 'execute_background_script', arguments: { scope: 'global', }, }); expect(result.isError).toBe(true); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); expect(textContent?.text).toBeDefined(); const errorData = JSON.parse(textContent?.text || '{}'); expect(errorData.success).toBe(false); expect(errorData.error?.code).toBe('MISSING_PARAMETER'); expect(errorData.error?.message).toContain('script'); }); it('should handle missing scope parameter', async () => { const result = await client.callTool({ name: 'execute_background_script', arguments: { script: 'gs.print("test");', }, }); expect(result.isError).toBe(true); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); expect(textContent?.text).toBeDefined(); const errorData = JSON.parse(textContent?.text || '{}'); expect(errorData.success).toBe(false); expect(errorData.error?.code).toBe('MISSING_PARAMETER'); expect(errorData.error?.message).toContain('scope'); }); it('should handle empty script parameter', async () => { const result = await client.callTool({ name: 'execute_background_script', arguments: { script: '', scope: 'global', }, }); expect(result.isError).toBe(true); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); expect(textContent?.text).toBeDefined(); const errorData = JSON.parse(textContent?.text || '{}'); expect(errorData.success).toBe(false); expect(errorData.error?.code).toBe('MISSING_PARAMETER'); }); it('should handle empty scope parameter', async () => { const result = await client.callTool({ name: 'execute_background_script', arguments: { script: 'gs.print("test");', scope: '', }, }); expect(result.isError).toBe(true); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); expect(textContent?.text).toBeDefined(); const errorData = JSON.parse(textContent?.text || '{}'); expect(errorData.success).toBe(false); expect(errorData.error?.code).toBe('MISSING_PARAMETER'); }); it('should handle invalid timeout parameter', async () => { const result = await client.callTool({ name: 'execute_background_script', arguments: { script: 'gs.print("test");', scope: 'global', timeout_ms: 500, // Too low }, }); expect(result.isError).toBe(true); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); expect(textContent?.text).toBeDefined(); const errorData = JSON.parse(textContent?.text || '{}'); expect(errorData.success).toBe(false); expect(errorData.error?.code).toBe('VALIDATION_ERROR'); }); it('should handle network errors gracefully', async () => { // Try with an invalid/unreachable instance const result = await client.callTool({ name: 'execute_background_script', arguments: { script: 'gs.print("test");', scope: 'global', }, }); // The call should return without throwing, but indicate an error expect(result.content).toBeDefined(); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); const responseData = JSON.parse(textContent?.text || '{}'); // Could be various errors depending on test environment expect(responseData.success === false || responseData.error).toBeDefined(); }, 15000); it('should handle unknown tools gracefully', async () => { await expect( client.callTool({ name: 'unknown_tool', arguments: {}, }) ).rejects.toThrow(); }); }); describe('Response Format', () => { it('should return JSON response', async () => { // Note: This test demonstrates the response format structure // In a real scenario, you'd mock the ServiceNow API const result = await client.callTool({ name: 'execute_background_script', arguments: { script: 'gs.print("test");', scope: 'global', }, }); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); expect(textContent).toBeDefined(); // Should be valid JSON (either success or error response) expect(() => JSON.parse(textContent?.text || '{}')).not.toThrow(); }, 15000); it('should include required fields in error responses', async () => { const result = await client.callTool({ name: 'execute_background_script', arguments: { script: '', scope: 'global', }, }); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); const errorResponse = JSON.parse(textContent?.text || '{}'); expect(errorResponse).toHaveProperty('success'); expect(errorResponse).toHaveProperty('error'); expect(errorResponse.error).toHaveProperty('code'); expect(errorResponse.error).toHaveProperty('message'); }); }); describe('Tool Parameters', () => { it('should accept script and scope as string parameters', async () => { // This test verifies the tool accepts the parameters correctly // The actual result depends on the ServiceNow instance availability expect(async () => { await client.callTool({ name: 'execute_background_script', arguments: { script: 'gs.print("Hello World");', scope: 'global', }, }); }).toBeDefined(); }); it('should handle optional timeout_ms parameter', async () => { expect(async () => { await client.callTool({ name: 'execute_background_script', arguments: { script: 'gs.print("Hello World");', scope: 'global', timeout_ms: 30000, }, }); }).toBeDefined(); }); it('should validate script length limits', async () => { // Create a script that's too long (over 50,000 characters) const longScript = 'gs.print("test");'.repeat(5000); // ~75,000 characters const result = await client.callTool({ name: 'execute_background_script', arguments: { script: longScript, scope: 'global', }, }); expect(result.isError).toBe(true); const textContent = (result.content as any[]).find((c: any) => c?.type === 'text'); const errorData = JSON.parse(textContent?.text || '{}'); expect(errorData.error?.code).toBe('INVALID_SCRIPT'); expect(errorData.error?.message).toContain('length'); }); }); describe('Security Warnings', () => { it('should include security warnings in tool description', async () => { const tools = await client.listTools(); const scriptTool = tools.tools.find((tool: any) => tool.name === 'execute_background_script'); expect(scriptTool?.description).toContain('SANDBOX ONLY'); expect(scriptTool?.description).toContain('arbitrary code'); }); }); describe('Global Context Overflow Prevention', () => { it('should include context protection in background script tool description', async () => { const tools = await client.listTools(); const scriptTool = tools.tools.find((tool: any) => tool.name === 'execute_background_script'); expect(scriptTool?.description).toContain('🛡️'); expect(scriptTool?.description).toContain('Auto-truncates'); }); it('should have concise tool descriptions', async () => { const tools = await client.listTools(); const scriptTool = tools.tools.find((tool: any) => tool.name === 'execute_background_script'); const tableTool = tools.tools.find((tool: any) => tool.name === 'execute_table_operation'); // Descriptions should be concise but informative expect(scriptTool?.description?.length).toBeLessThan(300); expect(tableTool?.description?.length).toBeLessThan(300); // Should still contain key information expect(scriptTool?.description).toContain('SANDBOX ONLY'); expect(tableTool?.description).toContain('CRUD operations'); }); it('should apply global protection to background script responses', async () => { // This test verifies that global protection is applied to background script responses // The actual result depends on the ServiceNow instance availability expect(async () => { await client.callTool({ name: 'execute_background_script', arguments: { script: 'gs.print("Test output");', scope: 'global', }, }); }).toBeDefined(); }); }); });

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/ClearSkye/SkyeNet-MCP-ACE'

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