Skip to main content
Glama
code_execution.py9.34 kB
"""Code execution tools for running TypeScript in Node.js environment.""" import asyncio import os import tempfile from pathlib import Path from typing import Any from mcp.server.fastmcp import FastMCP from ..core.config import get_config from ..core.validation import validate_params def register_code_execution_tools(mcp: FastMCP) -> None: """Register code execution MCP tools.""" @mcp.tool() @validate_params async def execute_typescript( code: str, timeout: int = 120 ) -> str: """Execute TypeScript code in a Node.js environment with access to Canvas API. This tool enables token-efficient bulk operations by executing code locally rather than loading all data into Claude's context. The code runs in a sandboxed Node.js environment with access to: - Canvas API credentials from environment - All TypeScript modules in src/canvas_mcp/code_api/ - Standard Node.js modules IMPORTANT: This achieves 99.7% token savings for bulk operations! Args: code: TypeScript code to execute. Can import from './canvas/*' modules. timeout: Maximum execution time in seconds (default: 120) Example Usage - Bulk Grading: ```typescript import { bulkGrade } from './canvas/grading/bulkGrade.js'; await bulkGrade({ courseIdentifier: "60366", assignmentId: "123", gradingFunction: (submission) => { // This runs locally - no token cost! const notebook = submission.attachments?.find( f => f.filename.endsWith('.ipynb') ); if (!notebook) return null; // Your grading logic here return { points: 100, rubricAssessment: { "_8027": { points: 100 } }, comment: "Great work!" }; } }); ``` Returns: Combined stdout and stderr from the execution, or error message if failed. Security: - Code runs in a temporary file that is deleted after execution - Inherits Canvas API credentials from server environment - Timeout enforced to prevent runaway processes """ config = get_config() # Get the absolute path to the code_api directory code_api_dir = Path(__file__).parent.parent / "code_api" # Create a temporary file for the code with tempfile.NamedTemporaryFile( mode='w', suffix='.ts', dir=code_api_dir, delete=False ) as temp_file: # Write the user's code temp_file.write(code) temp_file_path = temp_file.name try: # Prepare environment variables env = os.environ.copy() env['CANVAS_API_URL'] = config.canvas_api_url env['CANVAS_API_TOKEN'] = config.canvas_api_token # Get the repository root (where package.json is) repo_root = Path(__file__).parent.parent.parent.parent # Execute using tsx (faster than ts-node) or ts-node as fallback # tsx is a fast TypeScript execution engine that doesn't require compilation cmd = [ 'npx', 'tsx', # Try tsx first temp_file_path ] # Run the TypeScript code process = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=env, cwd=str(repo_root) ) try: stdout_bytes, stderr_bytes = await asyncio.wait_for( process.communicate(), timeout=timeout ) stdout = stdout_bytes.decode('utf-8', errors='replace') stderr = stderr_bytes.decode('utf-8', errors='replace') # Format output result_lines = [] if process.returncode == 0: result_lines.append("✅ TypeScript execution completed successfully\n") else: result_lines.append(f"❌ TypeScript execution failed with exit code {process.returncode}\n") if stdout: result_lines.append("=== Output ===") result_lines.append(stdout) if stderr: result_lines.append("=== Errors/Warnings ===") result_lines.append(stderr) return "\n".join(result_lines) if result_lines else "No output" except asyncio.TimeoutError: process.kill() await process.wait() return f"❌ Execution timed out after {timeout} seconds" except FileNotFoundError as e: return ( "❌ TypeScript execution environment not found.\n\n" "Please ensure Node.js and npx are installed:\n" " npm install -g tsx\n\n" f"Error: {str(e)}" ) except Exception as e: return f"❌ Execution error: {str(e)}" finally: # Clean up the temporary file try: os.unlink(temp_file_path) except Exception: pass # Ignore cleanup errors @mcp.tool() @validate_params async def list_code_api_modules() -> str: """List all available TypeScript modules in the code execution API. Returns a formatted list of all TypeScript files that can be imported in the execute_typescript tool, organized by category with descriptions. This helps Claude discover what operations are available for token-efficient bulk processing. Returns: Formatted string listing all available modules by category with descriptions. """ code_api_dir = Path(__file__).parent.parent / "code_api" if not code_api_dir.exists(): return "❌ Code API directory not found" # Module descriptions mapping module_descriptions = { "bulkGrade": "Grade multiple submissions with local processing function - most token-efficient method", "gradeWithRubric": "Grade a single submission with rubric criteria and optional comments", "bulkGradeDiscussion": "Grade discussion posts in bulk with local processing function", "listSubmissions": "Retrieve all submissions for an assignment (supports includeUser for names/emails)", "listCourses": "List all courses accessible to the current user", "getCourseDetails": "Get detailed information about a specific course", "sendMessage": "Send a message/announcement to course participants", "listDiscussions": "List discussion topics in a course", "postEntry": "Post an entry to a discussion topic", } # Organize modules by directory modules_by_category: dict[str, list[tuple[str, str]]] = {} for ts_file in code_api_dir.rglob("*.ts"): # Skip certain files if ts_file.name in ['index.ts', 'client.ts']: continue # Get relative path from code_api rel_path = ts_file.relative_to(code_api_dir) # Get category (parent directory name) category = rel_path.parent.name if rel_path.parent.name != '.' else 'root' # Get import path (convert .ts to .js for ESM imports) import_path = f"./{rel_path.parent}/{rel_path.stem}.js" # Get description from mapping module_name = rel_path.stem description = module_descriptions.get(module_name, "") if category not in modules_by_category: modules_by_category[category] = [] modules_by_category[category].append((import_path, description)) # Format output result_lines = [] result_lines.append("Available TypeScript Modules for Code Execution") result_lines.append("=" * 60) result_lines.append("") result_lines.append("Import these in execute_typescript tool:") result_lines.append("") for category, modules in sorted(modules_by_category.items()): result_lines.append(f"📁 {category.upper()}") result_lines.append("-" * 40) for import_path, description in sorted(modules, key=lambda x: x[0]): result_lines.append(f" {import_path}") if description: result_lines.append(f" • {description}") result_lines.append("") result_lines.append("Example Usage:") result_lines.append("```typescript") result_lines.append("import { bulkGrade } from './canvas/grading/bulkGrade.js';") result_lines.append("import { listSubmissions } from './canvas/assignments/listSubmissions.js';") result_lines.append("") result_lines.append("// Your code here...") result_lines.append("```") return "\n".join(result_lines)

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/vishalsachdev/canvas-mcp'

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