Skip to main content
Glama

Scratchpad MCP

by pc035860
mcp-project-scope.test.ts13.1 kB
/** * MCP Tools Project Scope Tests * * Tests the project_scope parameter handling in MCP tools layer */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { ScratchpadDatabase } from '../src/database/index.js'; import { createWorkflowTool, listWorkflowsTool, getLatestActiveWorkflowTool, updateWorkflowStatusTool, } from '../src/tools/workflow.js'; import { validateCreateWorkflowArgs, validateListWorkflowsArgs, validateGetLatestActiveWorkflowArgs, validateUpdateWorkflowStatusArgs, } from '../src/server-helpers.js'; describe('MCP Tools Project Scope Integration', () => { let db: ScratchpadDatabase; const testDbPath = ':memory:'; beforeEach(() => { db = new ScratchpadDatabase({ filename: testDbPath }); }); afterEach(() => { db.close(); }); describe('create-workflow tool with project_scope', () => { it('should create workflow with project_scope parameter', async () => { const createWorkflow = createWorkflowTool(db); const args = validateCreateWorkflowArgs({ name: 'Frontend Tasks', description: 'React development workflow', project_scope: 'my-react-app', }); const result = await createWorkflow(args); expect(result.workflow.name).toBe('Frontend Tasks'); expect(result.workflow.project_scope).toBe('my-react-app'); expect(result.message).toContain('(scope: my-react-app)'); }); it('should create workflow without project_scope (global)', async () => { const createWorkflow = createWorkflowTool(db); const args = validateCreateWorkflowArgs({ name: 'Global Tasks', description: 'Cross-project workflow', }); const result = await createWorkflow(args); expect(result.workflow.name).toBe('Global Tasks'); expect(result.workflow.project_scope).toBeNull(); expect(result.message).not.toContain('(scope:'); }); it('should validate project_scope parameter correctly', () => { // Valid string project_scope const validArgs = validateCreateWorkflowArgs({ name: 'Test', project_scope: 'my-project', }); expect(validArgs.project_scope).toBe('my-project'); // No project_scope (should be undefined) const noScopeArgs = validateCreateWorkflowArgs({ name: 'Test', }); expect(noScopeArgs.project_scope).toBeUndefined(); // Invalid project_scope type should throw expect(() => { validateCreateWorkflowArgs({ name: 'Test', project_scope: 123, // Invalid: not a string }); }).toThrow('project_scope must be a string'); }); }); describe('list-workflows tool with project_scope', () => { beforeEach(async () => { // Set up test data const createWorkflow = createWorkflowTool(db); await createWorkflow(validateCreateWorkflowArgs({ name: 'React Components', project_scope: 'frontend-app', })); await createWorkflow(validateCreateWorkflowArgs({ name: 'React Hooks', project_scope: 'frontend-app', })); await createWorkflow(validateCreateWorkflowArgs({ name: 'API Routes', project_scope: 'backend-api', })); await createWorkflow(validateCreateWorkflowArgs({ name: 'Global Utils', })); }); it('should list workflows for specific project_scope', async () => { const listWorkflows = listWorkflowsTool(db); const frontendArgs = validateListWorkflowsArgs({ project_scope: 'frontend-app', }); const result = await listWorkflows(frontendArgs); expect(result.workflows).toHaveLength(2); expect(result.count).toBe(2); expect(result.workflows.every(w => w.project_scope === 'frontend-app')).toBe(true); expect(result.workflows.map(w => w.name)).toContain('React Components'); expect(result.workflows.map(w => w.name)).toContain('React Hooks'); }); it('should list all workflows when no project_scope specified', async () => { const listWorkflows = listWorkflowsTool(db); const allArgs = validateListWorkflowsArgs({}); const result = await listWorkflows(allArgs); expect(result.workflows).toHaveLength(4); expect(result.count).toBe(4); }); it('should return empty list for non-existent project_scope', async () => { const listWorkflows = listWorkflowsTool(db); const nonExistentArgs = validateListWorkflowsArgs({ project_scope: 'non-existent-project', }); const result = await listWorkflows(nonExistentArgs); expect(result.workflows).toHaveLength(0); expect(result.count).toBe(0); }); it('should validate project_scope parameter correctly', () => { // Valid string project_scope const validArgs = validateListWorkflowsArgs({ project_scope: 'my-project', }); expect(validArgs.project_scope).toBe('my-project'); // No project_scope (should be undefined) const noScopeArgs = validateListWorkflowsArgs({}); expect(noScopeArgs.project_scope).toBeUndefined(); // Invalid project_scope type should throw expect(() => { validateListWorkflowsArgs({ project_scope: 123, // Invalid: not a string }); }).toThrow('project_scope must be a string'); }); }); describe('get-latest-active-workflow tool with project_scope', () => { let frontendWorkflow: any, backendWorkflow: any, globalWorkflow: any; beforeEach(async () => { const createWorkflow = createWorkflowTool(db); frontendWorkflow = await createWorkflow(validateCreateWorkflowArgs({ name: 'Frontend Workflow', project_scope: 'frontend-app', })); backendWorkflow = await createWorkflow(validateCreateWorkflowArgs({ name: 'Backend Workflow', project_scope: 'backend-api', })); globalWorkflow = await createWorkflow(validateCreateWorkflowArgs({ name: 'Global Workflow', })); }); it('should get latest active workflow for specific project_scope', async () => { const getLatestActiveWorkflow = getLatestActiveWorkflowTool(db); const frontendArgs = validateGetLatestActiveWorkflowArgs({ project_scope: 'frontend-app', }); const result = await getLatestActiveWorkflow(frontendArgs); expect(result.workflow).not.toBeNull(); expect(result.workflow?.name).toBe('Frontend Workflow'); expect(result.workflow?.project_scope).toBe('frontend-app'); expect(result.message).toContain('(scope: frontend-app)'); }); it('should return no workflow found message for non-existent project_scope', async () => { const getLatestActiveWorkflow = getLatestActiveWorkflowTool(db); const nonExistentArgs = validateGetLatestActiveWorkflowArgs({ project_scope: 'test-project' }); const result = await getLatestActiveWorkflow(nonExistentArgs); expect(result.workflow).toBeNull(); expect(result.message).toBe('No active workflow found in scope "test-project"'); }); it('should return null for project_scope with no workflows', async () => { const getLatestActiveWorkflow = getLatestActiveWorkflowTool(db); const nonExistentArgs = validateGetLatestActiveWorkflowArgs({ project_scope: 'non-existent-project', }); const result = await getLatestActiveWorkflow(nonExistentArgs); expect(result.workflow).toBeNull(); expect(result.message).toBe('No active workflow found in scope "non-existent-project"'); }); it('should respect active status when filtering by project_scope', async () => { const getLatestActiveWorkflow = getLatestActiveWorkflowTool(db); const updateWorkflowStatus = updateWorkflowStatusTool(db); // Deactivate the frontend workflow await updateWorkflowStatus(validateUpdateWorkflowStatusArgs({ workflow_id: frontendWorkflow.workflow.id, is_active: false, })); const frontendArgs = validateGetLatestActiveWorkflowArgs({ project_scope: 'frontend-app', }); const result = await getLatestActiveWorkflow(frontendArgs); expect(result.workflow).toBeNull(); expect(result.message).toBe('No active workflow found in scope "frontend-app"'); }); it('should validate project_scope parameter correctly', () => { // Valid string project_scope const validArgs = validateGetLatestActiveWorkflowArgs({ project_scope: 'my-project', }); expect(validArgs.project_scope).toBe('my-project'); // Missing project_scope should throw expect(() => { validateGetLatestActiveWorkflowArgs({}); }).toThrow('project_scope must be a string'); // Invalid project_scope type should throw expect(() => { validateGetLatestActiveWorkflowArgs({ project_scope: 123, // Invalid: not a string }); }).toThrow('project_scope must be a string'); }); }); describe('Multi-project MCP Integration Scenarios', () => { beforeEach(async () => { const createWorkflow = createWorkflowTool(db); // Create workflows for different projects await createWorkflow(validateCreateWorkflowArgs({ name: 'Frontend Dev', description: 'UI development tasks', project_scope: 'web-app', })); await createWorkflow(validateCreateWorkflowArgs({ name: 'Backend Dev', description: 'API development tasks', project_scope: 'api-server', })); await createWorkflow(validateCreateWorkflowArgs({ name: 'Mobile Dev', description: 'Mobile app tasks', project_scope: 'mobile-app', })); await createWorkflow(validateCreateWorkflowArgs({ name: 'DevOps Tasks', description: 'Infrastructure and deployment', })); // Global workflow }); it('should maintain complete isolation between projects', async () => { const listWorkflows = listWorkflowsTool(db); const getLatestActiveWorkflow = getLatestActiveWorkflowTool(db); // Each project should only see its own workflows const webAppWorkflows = await listWorkflows(validateListWorkflowsArgs({ project_scope: 'web-app', })); expect(webAppWorkflows.count).toBe(1); expect(webAppWorkflows.workflows[0].name).toBe('Frontend Dev'); const apiServerWorkflows = await listWorkflows(validateListWorkflowsArgs({ project_scope: 'api-server', })); expect(apiServerWorkflows.count).toBe(1); expect(apiServerWorkflows.workflows[0].name).toBe('Backend Dev'); const mobileAppWorkflows = await listWorkflows(validateListWorkflowsArgs({ project_scope: 'mobile-app', })); expect(mobileAppWorkflows.count).toBe(1); expect(mobileAppWorkflows.workflows[0].name).toBe('Mobile Dev'); // Latest active should be project-specific const webAppLatest = await getLatestActiveWorkflow(validateGetLatestActiveWorkflowArgs({ project_scope: 'web-app', })); expect(webAppLatest.workflow?.name).toBe('Frontend Dev'); const apiServerLatest = await getLatestActiveWorkflow(validateGetLatestActiveWorkflowArgs({ project_scope: 'api-server', })); expect(apiServerLatest.workflow?.name).toBe('Backend Dev'); // Global view should see all workflows const allWorkflows = await listWorkflows(validateListWorkflowsArgs({})); expect(allWorkflows.count).toBe(4); }); it('should handle workflow status changes independently per project', async () => { const updateWorkflowStatus = updateWorkflowStatusTool(db); const listWorkflows = listWorkflowsTool(db); const getLatestActiveWorkflow = getLatestActiveWorkflowTool(db); // Deactivate web-app workflow const webAppWorkflows = await listWorkflows(validateListWorkflowsArgs({ project_scope: 'web-app', })); await updateWorkflowStatus(validateUpdateWorkflowStatusArgs({ workflow_id: webAppWorkflows.workflows[0].id, is_active: false, })); // web-app should have no active workflows const webAppLatest = await getLatestActiveWorkflow(validateGetLatestActiveWorkflowArgs({ project_scope: 'web-app', })); expect(webAppLatest.workflow).toBeNull(); // Other projects should be unaffected const apiServerLatest = await getLatestActiveWorkflow(validateGetLatestActiveWorkflowArgs({ project_scope: 'api-server', })); expect(apiServerLatest.workflow).not.toBeNull(); expect(apiServerLatest.workflow?.name).toBe('Backend Dev'); const mobileAppLatest = await getLatestActiveWorkflow(validateGetLatestActiveWorkflowArgs({ project_scope: 'mobile-app', })); expect(mobileAppLatest.workflow).not.toBeNull(); expect(mobileAppLatest.workflow?.name).toBe('Mobile Dev'); }); }); });

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/pc035860/scratchpad-mcp'

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