Skip to main content
Glama

mcp-solver

MIT License
133
  • Linux
  • Apple
instructions.md26.6 kB
# MCP Solver – Quick Start Guide Welcome to the MCP Solver. This document provides concise guidelines on how to interact with the MCP Solver. This service provides access to MaxSAT (Maximum Satisfiability) optimization with a simplified interface for weighted constraint problems using WCNF (Weighted Conjunctive Normal Form). **IMPORTANT**: MaxSAT/PySAT is fully available in this environment. Always use the actual RC2 solver as shown in the examples. Never attempt manual optimization or create mock solvers. **YOU MUST USE ACTUAL RC2**: The environment has PySAT and RC2 pre-installed and ready to use. DO NOT create mock solvers or try to simulate optimization manually. If you have any issues with imports, check your syntax - the environment definitely has MaxSAT available. **CRITICAL**: The `export_solution` function is automatically available in the environment - you do NOT need to import it. Just use it directly as shown in the examples. ## Overview The MCP Solver integrates MaxSAT solving with the Model Context Protocol, allowing you to create, modify, and solve weighted constraint optimization problems. The following tools are available: - **clear_model** - **add_item** - **replace_item** - **delete_item** - **solve_model** These tools let you construct your model incrementally and solve it using a MaxSAT solver. ## List Semantics for Model Operations The model uses 0-based indexing and standard list semantics: - **add_item(index, content)**: Inserts code at position, shifting items at index and after to the right - Example: Items [0, 1, 2] → add_item(1, X) → [0, X, 1, 2] - Valid indices: 0 to length (can append at end) - **delete_item(index)**: Removes item, shifting subsequent items left - Example: Items [0, 1, 2, 3] → delete_item(1) → [0, 2, 3] - Valid indices: 0 to length-1 - **replace_item(index, content)**: Replaces item at index (no shifting) - Example: Items [0, 1, 2] → replace_item(1, X) → [0, X, 2] - Valid indices: 0 to length-1 **Critical**: Indices start at 0! First item is index 0, second is index 1, etc. ## 📋 MANDATORY Blueprint Structure **EVERY MaxSAT model MUST follow this exact 5-item structure (0-indexed):** ### Blueprint Template: ```python # Item 0: Imports and WCNF initialization from pysat.formula import WCNF from pysat.examples.rc2 import RC2 # Note: Helper functions (exactly_k, at_most_k, etc.) are already available - no import needed wcnf = WCNF() # Item 1: Problem parameters and variables # Define all variables with clear comments # var = number # Item 2: Hard constraints # Add all hard constraints (must be satisfied) # wcnf.append([literals]) # Item 3: Soft constraints # Add all soft constraints with weights # wcnf.append([literals], weight=value) # Item 4: Solve and export solution (MANDATORY) # MUST instantiate RC2, call compute(), and export results ``` ## 📋 Quick Reference Card ### MaxSAT Optimization Patterns ```python # MAXIMIZE value → Penalize NOT selecting wcnf.append([item], weight=value) # Penalty if item=FALSE # MINIMIZE cost → Penalize selecting wcnf.append([-item], weight=cost) # Penalty if item=TRUE # Exactly k items for clause in exactly_k(items, k): wcnf.append(clause) # Handle UNSAT if not solver.compute(): export_solution({"satisfiable": False}) ``` **Remember**: You penalize what you DON'T want! ## Quick Start Example ### Item 0: Imports and initialization ```python from pysat.formula import WCNF from pysat.examples.rc2 import RC2 # Note: Helper functions (exactly_k, at_most_k, etc.) are already available wcnf = WCNF() ``` ### Item 1: Variables ```python # Problem: Select items to maximize value # Variables: 1=itemA, 2=itemB, 3=itemC itemA = 1 itemB = 2 itemC = 3 ``` ### Item 2: Hard constraints ```python # Hard constraint: Can't select both A and B wcnf.append([-itemA, -itemB]) ``` ### Item 3: Soft constraints ```python # Soft constraints: We want to MAXIMIZE total value # Remember: MaxSAT MINIMIZES penalties, so we penalize NOT selecting valuable items wcnf.append([itemA], weight=5) # Lose 5 points if itemA=FALSE (not selected) wcnf.append([itemB], weight=3) # Lose 3 points if itemB=FALSE (not selected) wcnf.append([itemC], weight=2) # Lose 2 points if itemC=FALSE (not selected) # MaxSAT will select items to minimize penalties → maximizes value! ``` ### Item 4: Solve and export ```python # MANDATORY: You MUST instantiate and use the RC2 solver with RC2(wcnf) as solver: # MANDATORY: Call compute() to solve the problem model = solver.compute() # Returns variable assignments or None if model: # Solution found - model contains true variables # Create solution dictionary solution = { "itemA": itemA in model, "itemB": itemB in model, "itemC": itemC in model } # MANDATORY: Call export_solution with results export_solution({ "satisfiable": True, "cost": solver.cost, # Total weight of unsatisfied soft clauses "assignment": solution }) else: # No solution exists (hard constraints cannot be satisfied) export_solution({ "satisfiable": False, "message": "No solution exists" }) ``` ## ⚠️ Common Pitfalls - **Blueprint Violations**: ALWAYS use the 5-item structure (0-indexed). Do NOT combine or skip items! - **Missing RC2 Solver**: MUST instantiate RC2 solver and call compute() - don't just build the WCNF! - **Incorrect Problem Parameters**: ALWAYS encode the EXACT problem specifications - **Export Solution**: Always include `export_solution()` - it's automatically available - **Variable Ranges**: MaxSAT variables must be positive integers (1, 2, 3, ...) - **WCNF vs CNF**: Always use `WCNF()` for MaxSAT problems, not `CNF()` ## ❌ Common Mistakes to Avoid ### 1. Trying to Import Helper Functions ```python # ❌ WRONG - Don't import helpers from mcp_solver.maxsat.constraints import exactly_k from pysat.card import * # ✅ CORRECT - Helpers are already available for clause in exactly_k(vars, 3): wcnf.append(clause) ``` ### 2. Wrong Optimization Direction ```python # Problem: Maximize total value # ❌ WRONG - This minimizes value wcnf.append([-item], weight=value) # ✅ CORRECT - Penalty if not selected wcnf.append([item], weight=value) ``` ### 3. Forgetting to Add All Clauses from Helpers ```python # ❌ WRONG - Only adds one clause wcnf.append(exactly_k(vars, 2)) # ✅ CORRECT - Add all clauses returned for clause in exactly_k(vars, 2): wcnf.append(clause) ``` ### 4. Missing RC2 Solver Instantiation ```python # ❌ WRONG - Forgetting to use RC2 solver # Just building wcnf without solving wcnf = WCNF() # ... add constraints ... # No solver usage! # ✅ CORRECT - MUST instantiate and use RC2 with RC2(wcnf) as solver: model = solver.compute() if model: export_solution({...}) ``` ### 5. Missing Solution Export ```python # ❌ WRONG - No export model = solver.compute() if model: print(model) # Just printing, not exporting # ✅ CORRECT - Always export model = solver.compute() if model: export_solution({ "satisfiable": True, "cost": solver.cost, "solution": model }) ``` ## 🚨 CRITICAL: Problem Parameter Verification **NEVER modify problem parameters!** ### Before encoding ANY problem: 1. **Extract ALL numeric parameters** from the problem statement 2. **Add verification comments** in Item 1: ```python # Problem parameters (DO NOT CHANGE): # - Number of items: 10 # - Must select: exactly 3 # - Budget limit: 50 # Verify encoding matches: items = list(range(10)) # ✓ 10 items [0,1,2,3,4,5,6,7,8,9] required_selections = 3 # ✓ Exactly 3 items budget = 50 # ✓ Budget limit 50 ``` ## Weight Semantics in MaxSAT ### 🚨 CRITICAL PATTERN - GET THIS RIGHT! ```python # Problem: Maximize total value of selected items # ❌ WRONG - This MINIMIZES value (opposite of what you want!) for item, value in items.items(): wcnf.append([-item], weight=value) # NO! Penalizes selecting valuable items! # ✅ CORRECT - This MAXIMIZES value for item, value in items.items(): wcnf.append([item], weight=value) # YES! Penalizes NOT selecting valuable items! ``` **The #1 error in MaxSAT**: Getting the polarity backwards! ### Understanding Soft Constraints - **Soft clause weight** = penalty for NOT satisfying the clause - MaxSAT minimizes the total weight of unsatisfied soft clauses - Think of weights as "costs to pay" when a preference is violated ### ⚠️ CRITICAL: MaxSAT Minimizes UNSATISFIED Clause Weights! **This is COUNTERINTUITIVE and the #1 source of errors!** MaxSAT finds assignments that minimize the total weight of **violated** (unsatisfied) clauses. This means: - To **maximize** selecting valuable items → penalize **NOT** selecting them - To **minimize** selecting costly items → penalize selecting them ```python # 🚨 OPPOSITE OF INTUITION - We penalize NOT doing what we want! # Want to MAXIMIZE value? Penalize NOT selecting: wcnf.append([item], weight=value) # Penalty if item=FALSE # Want to MINIMIZE cost? Penalize selecting: wcnf.append([-item], weight=cost) # Penalty if item=TRUE ``` **Remember**: You're defining penalties for **bad** outcomes, not rewards for good ones! ### 🎯 Optimization Direction Quick Reference **Remember: MaxSAT always MINIMIZES the total penalty (cost)** 1. **To MAXIMIZE value/benefit** (want variable to be TRUE): ```python # If you want to maximize selecting items with values wcnf.append([var], weight=value) # Penalty = value if var is FALSE ``` 2. **To MINIMIZE cost** (want variable to be FALSE): ```python # If you want to minimize selecting items with costs wcnf.append([-var], weight=cost) # Penalty = cost if var is TRUE ``` ### Examples: ```python # Problem: Maximize total value of selected features # Feature A has value 10 wcnf.append([featureA], weight=10) # Lose 10 points if A not selected # Problem: Minimize total cost of selected items # Item B has cost 5 wcnf.append([-itemB], weight=5) # Pay 5 points if B is selected ``` ### Choosing Soft Constraint Weights When balancing multiple objectives (e.g., minimize cost while maximizing value): - **Start with raw values** from the problem statement - **Keep weights in similar ranges** for balanced optimization - **Only adjust if needed** - if the solution heavily favors one objective - **Example**: If costs are 1-5 and values are 10-50, consider scaling to similar ranges ### Encoding "At Least K" Requirements When a problem requires "select at least k items": - **If it's a hard requirement**: Use `at_least_k(variables, k)` as hard constraints - **If it's a soft preference with other objectives**: - Option 1: Add at_least_k as hard constraint if the requirement is explicit - Option 2: For true optimization, penalize shortfall: ```python # Penalize each missing item below k shortfall_vars = [] for i in range(1, k): var = wcnf.nv + 1 wcnf.nv = var shortfall_vars.append(var) # var is true if we have exactly i items (less than k) for clause in exactly_k(selected_vars, i): wcnf.append(clause + [-var]) wcnf.append([var], weight=penalty_per_missing * (k - i)) ``` ## Available Helper Functions The following helper functions are automatically available in your code (no import needed): ### Cardinality Constraints: - `exactly_k(variables, k)` - Returns clauses for exactly k variables true - `at_most_k(variables, k)` - Returns clauses for at most k variables true - `at_least_k(variables, k)` - Returns clauses for at least k variables true - `exactly_one(variables)` - Returns clauses for exactly one variable true - `at_most_one(variables)` - Returns clauses for at most one variable true ### Logical Constraints: - `implies(a, b)` - Returns clause for: if a then b - `mutually_exclusive(variables)` - Same as at_most_one (mutually exclusive) - `if_then_else(condition, then_var, else_var)` - If-then-else construct ### ⚠️ IMPORTANT: No Import Needed! These functions are **embedded** in the environment. Just use them directly: ```python # Item 2: Hard constraints # CORRECT - Just use the function directly for clause in exactly_k([item1, item2, item3], 2): wcnf.append(clause) # WRONG - Do NOT import anything # from mcp_solver.maxsat.constraints import exactly_k # ❌ NO! ``` ## Example: Feature Selection ### Item 0: Imports ```python from pysat.formula import WCNF from pysat.examples.rc2 import RC2 # Note: Helper functions are already available wcnf = WCNF() ``` ### Item 1: Variables ```python # PARAMETER VERIFICATION # Problem parameters (DO NOT CHANGE): # - Number of features: 6 # - Feature values: cloud=10, ai=8, security=6, mobile=7, analytics=9, integration=5 # Variables: 1=cloud, 2=ai, 3=security, 4=mobile, 5=analytics, 6=integration cloud = 1 ai = 2 security = 3 mobile = 4 analytics = 5 integration = 6 # Feature values for soft constraints values = { cloud: 10, ai: 8, security: 6, mobile: 7, analytics: 9, integration: 5 } ``` ### Item 2: Hard constraints ```python # Hard constraints: Dependencies # If AI selected, must have cloud wcnf.append([-ai, cloud]) # NOT ai OR cloud # If analytics selected, must have integration wcnf.append([-analytics, integration]) ``` ### Item 3: Soft constraints ```python # Goal: Maximize total value of selected features # Strategy: Penalize NOT selecting each feature by its value # This is COUNTERINTUITIVE but correct! for feature, value in values.items(): wcnf.append([feature], weight=value) # Penalty = value if feature=FALSE # Example: cloud (value=10) not selected → penalty of 10 # MaxSAT minimizes penalties → selects high-value features ``` ### Item 4: Solve and export ```python with RC2(wcnf) as solver: model = solver.compute() if model: # Extract selected features selected_features = { "cloud": cloud in model, "ai": ai in model, "security": security in model, "mobile": mobile in model, "analytics": analytics in model, "integration": integration in model } # Calculate total value total_value = sum(values[f] for f in model if f in values) export_solution({ "satisfiable": True, "cost": solver.cost, "selected_features": selected_features, "total_value": total_value }) else: export_solution({ "satisfiable": False, "message": "No valid feature selection exists" }) ``` ## Example: Nurse Scheduling ### Item 0: Imports ```python from pysat.formula import WCNF from pysat.examples.rc2 import RC2 # Note: Helper functions are already available wcnf = WCNF() ``` ### Item 1: Variables ```python # PARAMETER VERIFICATION # Problem parameters (DO NOT CHANGE): # - Number of nurses: 5 # - Number of shifts: 3 # - Nurses per shift: exactly 2 # - Preferred shifts per nurse: 1-2 num_nurses = 5 num_shifts = 3 nurses_per_shift = 2 # Variables: nurse_shift[n][s] = nurse n works shift s var_count = 0 nurse_shift = {} for n in range(num_nurses): for s in range(num_shifts): var_count += 1 nurse_shift[(n, s)] = var_count # Verify parameters assert len(nurse_shift) == num_nurses * num_shifts, "Wrong number of variables" ``` ### Item 2: Hard constraints ```python # Hard constraint: Exactly 2 nurses per shift for shift in range(num_shifts): shift_vars = [nurse_shift[(n, shift)] for n in range(num_nurses)] for clause in exactly_k(shift_vars, nurses_per_shift): wcnf.append(clause) ``` ### Item 3: Soft constraints ```python # Soft preference: Each nurse works at least 1 shift (weight 10) for nurse in range(num_nurses): nurse_vars = [nurse_shift[(nurse, s)] for s in range(num_shifts)] wcnf.append(nurse_vars, weight=10) # At least one shift # Soft preference: Discourage working all 3 shifts (weight 5) for nurse in range(num_nurses): # Penalize if nurse works all shifts wcnf.append([ -nurse_shift[(nurse, 0)], -nurse_shift[(nurse, 1)], -nurse_shift[(nurse, 2)] ], weight=5) ``` ### Item 4: Solve and export ```python with RC2(wcnf) as solver: model = solver.compute() if model: schedule = {} for n in range(num_nurses): schedule[f"nurse_{n}"] = [] for s in range(num_shifts): if nurse_shift[(n, s)] in model: schedule[f"nurse_{n}"].append(f"shift_{s}") # Verify solution meets requirements for s in range(num_shifts): assigned = sum(1 for n in range(num_nurses) if nurse_shift[(n, s)] in model) assert assigned == nurses_per_shift export_solution({ "satisfiable": True, "cost": solver.cost, "schedule": schedule }) else: export_solution({ "satisfiable": False, "message": "No valid schedule exists" }) ``` ## Common Encoding Patterns ### Pattern 1: Maximizing Value Selection When you want to maximize the total value of selected items: ```python # WRONG ❌ - This MINIMIZES value! for item, value in item_values.items(): wcnf.append([item], weight=value) # Penalizes selecting valuable items # CORRECT ✅ - Penalize NOT selecting valuable items for item, value in item_values.items(): wcnf.append([item], weight=value) # Penalty = value if item NOT selected ``` **Why this works**: MaxSAT minimizes penalties. By penalizing NOT selecting an item by its value, MaxSAT will select high-value items to avoid large penalties. ### Pattern 2: Minimizing Cost Selection When you want to minimize the total cost of selected items: ```python # WRONG ❌ - This MAXIMIZES cost! for item, cost in item_costs.items(): wcnf.append([item], weight=cost) # Penalizes NOT selecting costly items # CORRECT ✅ - Penalize selecting costly items for item, cost in item_costs.items(): wcnf.append([-item], weight=cost) # Penalty = cost if item IS selected ``` **Why this works**: By penalizing the selection of an item by its cost, MaxSAT will avoid selecting high-cost items. ### Pattern 3: Bonus for Relationship (Auxiliary Variables) When you want to give a bonus if two things happen together: ```python # Problem: Give bonus of 5 if both personA and personB are selected # Step 1: Create auxiliary variable aux_var = max(all_variables) + 1 # Next available variable number # Step 2: Link auxiliary to the relationship # aux is true IFF both persons are selected wcnf.append([-personA, -personB, aux_var]) # personA AND personB => aux wcnf.append([personA, -aux_var]) # aux => personA wcnf.append([personB, -aux_var]) # aux => personB # Step 3: Give bonus when auxiliary is true wcnf.append([aux_var], weight=5) # Penalty of 5 if aux is FALSE (bonus not given) ``` **Why this works**: The auxiliary variable tracks whether both conditions are met. We penalize NOT having the auxiliary true, which encourages satisfying both conditions. ### Pattern 4: Exactly One Relationship Bonus When multiple pairs could get a bonus, but you want to count it only once: ```python # Problem: Carol and Emma get bonus 3 if in same room (count once, not per room) # Create one auxiliary for the relationship same_room_aux = max_var + 1 # For each room, if both are in it, auxiliary must be true for room in rooms: carol_in_room = person_room[(carol_id, room)] emma_in_room = person_room[(emma_id, room)] wcnf.append([-carol_in_room, -emma_in_room, same_room_aux]) # If auxiliary is true, they must be in some room together room_clauses = [] for room in rooms: room_clauses.extend([-same_room_aux, person_room[(carol_id, room)]]) room_clauses.extend([-same_room_aux, person_room[(emma_id, room)]]) wcnf.append(room_clauses) # Give bonus once wcnf.append([same_room_aux], weight=3) # Penalty if NOT in same room ``` ### Pattern 5: Conditional Penalties When you want to penalize something only if a condition is met: ```python # Problem: If itemA is selected, penalize selecting itemB by 10 # Create auxiliary: aux = (itemA AND itemB) aux = max_var + 1 wcnf.append([-itemA, -itemB, aux]) # itemA AND itemB => aux wcnf.append([itemA, -aux]) # aux => itemA wcnf.append([itemB, -aux]) # aux => itemB # Penalize the bad combination wcnf.append([-aux], weight=10) # Penalty if both are selected ``` ## Solution Export ### CRITICAL: Always Export Your Solution **You MUST call `export_solution()` to return results.** This function is automatically available in the environment. `export_solution(data)`: Extract and format solutions from a MaxSAT solver or solution data This function processes MaxSAT solution data and creates a standardized output format. It supports both direct dictionary input and RC2 MaxSAT solver objects. ### Basic Usage: ```python # Method 1: Direct dictionary (recommended) export_solution({ "satisfiable": True, "cost": solver.cost, "selected_items": selected, "total_value": total }) # Method 2: Pass RC2 solver directly export_solution(solver, variables=var_mapping, objective=total_value) ``` ### Important Notes: - Requires at minimum: `{"satisfiable": True/False}` - Structure data using custom dictionaries that reflect your problem domain - For optimization problems, include `cost` from the solver - The function marks solutions with `_is_maxsat_solution` for identification ## Handling Unsatisfiable Problems ### When MaxSAT Returns UNSATISFIABLE If the RC2 solver cannot find a solution: ```python with RC2(wcnf) as solver: if solver.compute(): # Process solution as normal export_solution({ "satisfiable": True, "cost": solver.cost, # ... solution details ... }) else: # Problem is UNSATISFIABLE export_solution({ "satisfiable": False, "message": "No solution exists - hard constraints cannot all be satisfied" }) ``` ### ⚠️ CRITICAL: Never Remove Constraints! When you encounter unsatisfiability: 1. **DO NOT** remove constraints to make the problem satisfiable 2. **DO NOT** modify the problem to find "some" solution 3. **DO** report that the problem is unsatisfiable 4. **DO** explain which constraints likely conflict if possible ### Example: Detecting Conflicting Constraints ```python # Problem: Schedule 3 items in 2 slots, each slot holds at most 1 item # This is UNSATISFIABLE (3 items can't fit in 2 single-item slots) # After adding constraints and solving: with RC2(wcnf) as solver: if not solver.compute(): export_solution({ "satisfiable": False, "message": "Cannot fit 3 items in 2 slots with capacity 1 each", "conflict": "3 items require 3 slots, but only 2 available" }) ``` ## ✅ Final Checklist Before submitting your solution, verify: - [ ] **Item 0**: Imports WCNF and RC2, creates wcnf = WCNF() - [ ] **Item 1**: Defines all variables with clear comments and parameter verification - [ ] **Item 2**: Adds all hard constraints using wcnf.append() - [ ] **Item 3**: Adds all soft constraints with appropriate weights - [ ] **Item 4**: MANDATORY - Instantiates RC2(wcnf), calls solver.compute(), and exports solution - [ ] **Parameters**: All problem parameters match exactly (no modifications!) - [ ] **Variables**: All variables are positive integers - [ ] **Export**: Solution includes satisfiable, cost, and problem-specific data ## Final Notes - **Blueprint Compliance**: ALWAYS follow the 5-item structure (0-indexed) - **Direct Approach**: Build constraints explicitly without abstractions - **Weight Semantics**: Remember weights are penalties for UNSATISFIED clauses - **Review Output**: Check confirmation messages after each tool call ## Important: Two Types of Indexing 1. **Model Item Indexing**: Always 0-based - First item is at index 0 (imports) - Used with add_item, replace_item, delete_item - Example: `add_item(0, "# Import statements")` adds at the beginning 2. **SAT Variable Numbering**: Must be positive integers (1, 2, 3, ...) - MaxSAT requires variables to be numbered starting from 1 - Variable 0 is invalid in SAT solvers - Example: `item1 = 1`, `item2 = 2`, etc. ## Variable Mapping Pattern When mapping problem entities to SAT variables: ```python # Good: Clear mapping with 1-based variables var_count = 0 nurse_shift = {} for n in range(num_nurses): # 0-based iteration for s in range(num_shifts): var_count += 1 nurse_shift[(n, s)] = var_count # 1-based variable # Alternative: Direct formula def get_var(nurse, shift, num_shifts): return nurse * num_shifts + shift + 1 # +1 for 1-based ``` **Remember:** Iterate with 0-based indices, map to 1-based variables! ## Troubleshooting ### "NameError" or Import Errors If you see errors like `NameError: name 'RC2' is not defined` or `__build_class__ not found`, the issue is likely a **syntax error** in your code, not a missing import. The environment has all necessary imports available. **Common causes:** - Missing closing parenthesis, bracket, or quote - Incorrect indentation - Typo in variable or function names **Solution:** Carefully check your syntax. The error message will usually point to the line with the issue. ### Common mistakes to avoid: 1. **Creating fake solver objects** ❌ ```python # NEVER DO THIS - The environment has real RC2! import types solver = types.SimpleNamespace() solver.compute = lambda: [1, 2, 3] # compute() returns the model directly ``` 2. **Trying to mock MaxSAT optimization** ❌ ```python # NEVER DO THIS - Use actual RC2! def fake_optimize(wcnf): # Manual "optimization" logic return some_solution ``` 3. **Assuming the environment doesn't have MaxSAT** ❌ ```python # NEVER DO THIS print("MaxSAT not available, using heuristic instead") ``` **REMEMBER**: The environment has PySAT and RC2 fully installed and ready to use. Always use the actual MaxSAT solver as shown in the examples. If you encounter any import errors, it's likely a syntax error in your code, not a missing module.

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/szeider/mcp-solver'

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