recommend_next
Analyzes your current MCP server stack and a new development context to recommend the next server to add, with reasoning for the choice.
Instructions
Mid-project advisor: given your current MCP stack (comma-separated server names) and a new development context, recommend what to add next and why. Example: current_stack="github,filesystem", new_context="adding Stripe payments and PDF invoices"
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| current_stack | Yes | ||
| new_context | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/mcpilot/server.py:76-94 (handler)The main handler function for the 'recommend_next' tool. It accepts current_stack (comma-separated server names) and new_context, finds semantically similar servers excluding already-installed ones, and returns a formatted recommendation.
def recommend_next(current_stack: str, new_context: str) -> str: """ Mid-project advisor: given your current MCP stack (comma-separated server names) and a new development context, recommend what to add next and why. Example: current_stack="github,filesystem", new_context="adding Stripe payments and PDF invoices" """ try: _ensure_index() installed = [s.strip() for s in current_stack.split(",") if s.strip()] results = find_similar(new_context, top_k=5, exclude=installed) header = ( f"## What to add next\n" f"**Current stack:** {current_stack}\n" f"**New context:** {new_context}\n\n" ) return header + _format_results(results, new_context) except Exception as e: return _error_response("computing next recommendations", e) - src/mcpilot/server.py:75-76 (registration)The tool is registered via the @mcp.tool() decorator on line 75, which is a FastMCP decorator that registers the function as an MCP tool named 'recommend_next'.
@mcp.tool() def recommend_next(current_stack: str, new_context: str) -> str: - src/mcpilot/server.py:35-42 (helper)Error formatting helper used by recommend_next when an exception occurs.
def _error_response(context: str, exc: Exception) -> str: traceback.print_exc(file=sys.stderr) return ( f"## Error while {context}\n\n" f"{type(exc).__name__}: {exc}\n\n" f"See server logs for details. If this persists, try rebuilding the " f"index with `uv run python -m mcpilot.indexer --force`." ) - src/mcpilot/server.py:45-57 (helper)Result formatting helper used by recommend_next to format the list of recommended servers into markdown.
def _format_results(results: list[dict], project_description: str) -> str: if not results: return "No matching MCP servers found. Try a more descriptive project description." lines = [] for i, r in enumerate(results, 1): rationale = generate_rationale(r, project_description) lines.append( f"{i}. **{r['name']}**\n" f" {r['url']}\n" f" {rationale}\n" ) return "\n".join(lines) - src/mcpilot/search.py:28-86 (helper)The semantic search function called by recommend_next. Takes a query string and optional exclude list to filter out already-installed servers.
def find_similar( query: str, top_k: int = 10, exclude: list[str] | None = None, min_score: float = DEFAULT_MIN_SCORE, ) -> list[dict]: """ Return top_k servers most semantically similar to query. Each result: {name, url, description, category, score} """ model = _get_model() query_emb = model.encode([query])[0].tolist() excluded_lower = {n.lower() for n in (exclude or [])} fetch_limit = top_k + max(len(excluded_lower) * 3, 10) con = get_connection() rows = con.execute( """ SELECT name, description, url, category, score FROM ( SELECT name, description, url, category, array_cosine_similarity(embedding, ?::FLOAT[384]) AS score FROM servers ) WHERE score >= ? ORDER BY score DESC LIMIT ? """, [query_emb, min_score, fetch_limit], ).fetchall() con.close() results = [] for name, desc, url, cat, score in rows: name_lower = name.lower() short_name = name_lower.split("/")[-1] if any( e == name_lower or e == short_name or e in name_lower for e in excluded_lower ): continue results.append( { "name": name, "url": url, "description": desc, "category": cat, "score": float(score), } ) if len(results) >= top_k: break # Adaptive fallback: if nothing cleared the threshold, return the closest # matches below it so callers never silently get zero results. if not results and min_score > 0: return find_similar(query, top_k=top_k, exclude=exclude, min_score=0.0) return results