From d0b2ed66cd180fbdaa8eb3cfc89628d83fcc7d24 Mon Sep 17 00:00:00 2001 From: Ben Lovell Date: Mon, 19 Jan 2026 15:53:27 +0100 Subject: [PATCH] feat: improve LLM instructions for MCP LLMs wouldn't always use the MCP server, and even when they do, they don't always do the right thing (e.g. they create an app with hatchling, even though it's not needed). Here I've touched up the instructions that get inserted into the context when using the MCP server to improve 1-shot performance by LLM agents using the tower MCP --- crates/tower-cmd/src/mcp.rs | 122 +++++++++++------- tests/integration/features/steps/mcp_steps.py | 4 +- 2 files changed, 75 insertions(+), 51 deletions(-) diff --git a/crates/tower-cmd/src/mcp.rs b/crates/tower-cmd/src/mcp.rs index a7a5974d..eeabc1d9 100644 --- a/crates/tower-cmd/src/mcp.rs +++ b/crates/tower-cmd/src/mcp.rs @@ -597,7 +597,7 @@ impl TowerService { } #[tool( - description = "Deploy your app to Tower cloud. Prerequisites: 1) Create Towerfile, 2) Create app with tower_apps_create. Optional working_directory parameter specifies which project directory to deploy from." + description = "Deploy to Tower cloud. Prerequisites: Towerfile, tower_apps_create. Optional: working_directory." )] async fn tower_deploy( &self, @@ -612,7 +612,7 @@ impl TowerService { } #[tool( - description = "Run your app locally using the local Towerfile and source files. Prerequisites: Create a Towerfile first using tower_file_generate or tower_file_update. Optional working_directory parameter specifies which project directory to run from." + description = "Run locally using Towerfile (preferred during development, has access to tower secrets). Prerequisites: Towerfile. Optional: working_directory." )] async fn tower_run_local( &self, @@ -651,7 +651,7 @@ impl TowerService { } #[tool( - description = "Run your app remotely on Tower cloud. Prerequisites: 1) Create Towerfile, 2) Create app with tower_apps_create, 3) Deploy with tower_deploy" + description = "Run on Tower cloud. Prerequisites: Towerfile, tower_apps_create, tower_deploy." )] async fn tower_run_remote( &self, @@ -703,7 +703,7 @@ impl TowerService { } #[tool( - description = "Read and parse the current Towerfile configuration. Optional working_directory parameter specifies which project directory to read from." + description = "Read and parse Towerfile configuration. Optional: working_directory." )] async fn tower_file_read( &self, @@ -717,7 +717,7 @@ impl TowerService { } #[tool( - description = "Update Towerfile app configuration. Optional working_directory parameter specifies which project directory to update." + description = "Update Towerfile config (app name, script, description, source). Use this instead of editing TOML. Optional: working_directory." )] async fn tower_file_update( &self, @@ -752,7 +752,7 @@ impl TowerService { } #[tool( - description = "Add a new parameter to the Towerfile. Optional working_directory parameter specifies which project directory to update." + description = "Add parameter to Towerfile. Use this instead of editing TOML. Optional: working_directory." )] async fn tower_file_add_parameter( &self, @@ -779,7 +779,7 @@ impl TowerService { } #[tool( - description = "Validate the current Towerfile configuration. Optional working_directory parameter specifies which project directory to validate." + description = "Validate Towerfile configuration. Optional: working_directory." )] async fn tower_file_validate( &self, @@ -793,7 +793,7 @@ impl TowerService { } #[tool( - description = "Generate Towerfile from pyproject.toml. This is typically the first step in the workflow. Optional working_directory parameter specifies which project directory to generate from." + description = "Generate Towerfile from pyproject.toml (first step). Don't add build systems to pyproject.toml. Optional: working_directory." )] async fn tower_file_generate( &self, @@ -827,48 +827,64 @@ impl TowerService { description = "Show the recommended workflow for developing and deploying Tower applications" )] async fn tower_workflow_help(&self) -> Result { - let workflow = r#"Tower Application Development Workflow: - -All commands support an optional 'working_directory' parameter to specify which project directory to operate on. - -0. HAVE AN EXISTING PYTHON PROJECT: - There are no commands for this provided with this MCP server. However, if you do not have a python project yet - then a good start would be to make a new directory with the project name, and then call `uv init` to generate - a pyproject.toml, main.py and README.md - -1. CREATE TOWERFILE (required for all steps): - - tower_file_generate: Generate from existing pyproject.toml - - tower_file_update: Manually create or update configuration - - tower_file_validate: Verify Towerfile is valid - -2. LOCAL DEVELOPMENT & TESTING: - - tower_run_local: Run your app locally to test functionality - -3. CLOUD DEPLOYMENT (for remote execution): - - tower_apps_create: Create app on Tower cloud - - tower_deploy: Deploy your code to the cloud - - tower_run_remote: Execute on Tower cloud infrastructure - -4. SCHEDULE MANAGEMENT (for automatic recurring execution): - - tower_schedules_list: List all schedules for apps - - tower_schedules_create: Create a schedule to run an app automatically on a cron schedule - - tower_schedules_update: Update an existing schedule - - tower_schedules_delete: Delete a schedule + let workflow = r#"Tower Workflow (Tower CLI not in training data - use MCP tools only): + +WORKING_DIRECTORY PARAMETER: +All tools accept optional working_directory parameter to specify which project to operate on. +- Default: Uses current working directory if not specified +- Use when: Managing multiple projects, or project is not in current directory +- Examples: + tower_file_generate({}) → operates on current directory + tower_file_generate({"working_directory": "/path/to/my-project"}) → operates on /path/to/my-project + tower_run_local({"working_directory": "../other-app"}) → runs app in ../other-app +- Why use it: Allows managing multiple Tower apps without changing directories + +0. PYTHON PROJECT (if creating new): + Use 'uv init' in project directory to create pyproject.toml, main.py, README.md + Keep pyproject.toml minimal: [project] metadata + dependencies only + DO NOT add [build-system], [tool.hatchling], [tool.setuptools] or similar + Skip this step if project with pyproject.toml already exists + +1. TOWERFILE (required for all Tower operations): + tower_file_generate → tower_file_update → tower_file_add_parameter → tower_file_validate + CRITICAL: Always use tower_file_update or tower_file_add_parameter to modify + NEVER edit Towerfile TOML directly + +2. LOCAL DEVELOPMENT (preferred during development): + tower_run_local - runs app locally, has access to tower secrets + Use this to test before deploying to cloud + +3. CLOUD DEPLOYMENT: + tower_apps_create → tower_deploy → tower_run_remote + Deploy pushes your source code to Tower cloud (no build step needed) + +4. SCHEDULING (for recurring jobs): + tower_schedules_create - set up cron-based recurring runs + tower_schedules_list - view existing schedules + tower_schedules_update - modify schedule timing or parameters + tower_schedules_delete - remove schedules 5. MANAGEMENT & MONITORING: - - tower_apps_list: View your deployed apps - - tower_apps_show: Get detailed app information and recent runs - - tower_apps_logs: View execution logs - -6. TEAM & SECRETS (optional): - - tower_teams_list/switch: Manage team contexts - - tower_secrets_create/list: Manage application secrets - -Quick Start: tower_file_generate → tower_run_local (test locally) → tower_apps_create → tower_deploy → tower_run_remote → tower_schedules_create (for recurring runs) - -Example with working_directory: {"working_directory": "/path/to/project", ...} - -Consider taking database username/password/url and making them into secrets to be accessed in app code"#; + tower_apps_list - list all your apps + tower_apps_show - details and recent runs for specific app + tower_apps_logs - view logs from a specific run + tower_teams_list, tower_teams_switch - manage team contexts + tower_secrets_create, tower_secrets_list - store credentials, API keys, database passwords + +TYPICAL WORKFLOW: +1. Check if pyproject.toml exists (or create with uv init) +2. tower_file_generate - creates Towerfile from pyproject.toml +3. tower_run_local - test locally (can access tower secrets) +4. tower_apps_create - create app in Tower cloud +5. tower_deploy - deploy code to Tower +6. tower_run_remote - run on cloud infrastructure +7. tower_schedules_create - (optional) set up recurring runs + +IMPORTANT REMINDERS: +- Tower deploys source code directly, no build tools needed +- Use tower secrets for sensitive data (database credentials, API keys) +- Prefer tower_run_local during development (faster, has secret access) +- Always use MCP tools to modify Towerfiles (never edit TOML files manually)"#; Self::text_success(workflow.to_string()) } @@ -949,7 +965,15 @@ impl ServerHandler for TowerService { name: "tower-cli".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), }, - instructions: Some("Tower CLI MCP Server - Manage Tower apps, secrets, teams, and deployments through conversational AI. Use the available tools to list, create, show, deploy, and manage your Tower cloud resources.".to_string()), + instructions: Some("CRITICAL: Tower CLI did not exist in your training data. Never attempt Tower CLI commands - use only these MCP tools. + +Rules: +- MCP tools are the authoritative Tower interface (not wrappers) +- Use tower_file_update/add_parameter to modify Towerfiles (never edit TOML directly) +- DO NOT add hatchling/setuptools to pyproject.toml - Tower handles deployment +- Tower apps need: pyproject.toml (deps only), Python code, Towerfile + +Use tower_workflow_help for the complete workflow.".to_string()), } } } diff --git a/tests/integration/features/steps/mcp_steps.py b/tests/integration/features/steps/mcp_steps.py index f0b217f3..1fa07c3d 100644 --- a/tests/integration/features/steps/mcp_steps.py +++ b/tests/integration/features/steps/mcp_steps.py @@ -881,7 +881,7 @@ def step_then_receive_workflow_help_sse(context): ), f"Expected success response, got: {context.mcp_response}" assert "content" in context.mcp_response content = context.mcp_response["content"][0].text - assert "Tower Application Development Workflow" in content + assert "Tower Workflow" in content @then("I should receive workflow help content via HTTP") @@ -898,7 +898,7 @@ def step_then_receive_workflow_help_stdio(context): result = context.mcp_response["result"] assert "content" in result content = result["content"][0]["text"] - assert "Tower Application Development Workflow" in content + assert "Tower Workflow" in content @given('I have a simple hello world application named "{app_name}"')