We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/tokusumi/wassden-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""CLI interface for wassden."""
import asyncio
from importlib import metadata
from pathlib import Path
from typing import Annotated, Any
import typer
from wassden.clis.utils import (
_determine_language_for_file,
_determine_language_for_user_input,
_supports_color,
print_error,
print_info,
print_warning,
)
from wassden.handlers import (
handle_analyze_changes,
handle_check_completeness,
handle_generate_review_prompt,
handle_get_traceability,
handle_prompt_code,
handle_prompt_design,
handle_prompt_requirements,
handle_prompt_tasks,
handle_validate_design,
handle_validate_requirements,
handle_validate_tasks,
)
from wassden.language_types import Language
from wassden.server import main as run_server_with_transport
from wassden.types import SpecDocuments, TransportType
async def run_handler(handler: Any, args: dict[str, Any]) -> None:
"""Run a handler and print the result."""
try:
result = await handler(args)
content = result.get("content", [])
if content and len(content) > 0:
text = content[0].get("text", "")
typer.echo(text)
else:
print_warning("No content returned from handler")
except Exception as e:
print_error(f"Error: {e!s}")
# Don't exit with error code - handle gracefully
async def run_handler_typed(handler: Any, *args: Any) -> None:
"""Run a typed handler and print the result."""
try:
result = await handler(*args)
# Convert Pydantic model to dict if needed
result_dict = result.model_dump() if hasattr(result, "model_dump") else result
content = result_dict.get("content", [])
if content and len(content) > 0:
text = content[0].get("text", "")
typer.echo(text)
else:
print_warning("No content returned from handler")
except Exception as e:
print_error(f"Error: {e!s}")
# Don't exit with error code - handle gracefully
app = typer.Typer(
help="wassden - MCP-based Spec-Driven Development toolkit.",
rich_markup_mode="markdown" if _supports_color() else None,
pretty_exceptions_enable=_supports_color(),
)
@app.command()
def start_mcp_server(
transport: Annotated[TransportType, typer.Option(help="Transport type")] = TransportType.STDIO,
host: Annotated[str, typer.Option(help="HTTP host (only used for sse/streamable-http transports)")] = "127.0.0.1",
port: Annotated[int, typer.Option(help="HTTP port (only used for sse/streamable-http transports)")] = 3001,
) -> None:
"""Start wassden MCP server with specified transport."""
print_info(f"Starting wassden MCP server with {transport.value} transport...")
if transport in [TransportType.SSE, TransportType.STREAMABLE_HTTP]:
print_info(f"Listening on {host}:{port}")
run_server_with_transport(transport=transport.value, host=host, port=port)
# Async implementation
# Async implementation
async def _prompt_requirements_async(userinput: str, force: bool, language: Language | None) -> None:
"""Async implementation for requirements prompt generation."""
determined_language = _determine_language_for_user_input(language, userinput)
if force:
# Force mode: generate requirements prompt without completeness verification
specs = SpecDocuments(
requirements_path=Path("specs/requirements.md"),
design_path=Path("specs/design.md"),
tasks_path=Path("specs/tasks.md"),
language=determined_language,
)
await run_handler_typed(
handle_prompt_requirements,
specs,
userinput,
"",
"",
)
else:
# Default mode: check completeness
await run_handler_typed(handle_check_completeness, userinput, determined_language)
@app.command()
def prompt_requirements(
userinput: Annotated[
str,
typer.Option(
"--userInput", "-i", help="Detailed description of your project including goals, features, and context"
),
],
force: Annotated[
bool,
typer.Option(
"--force",
"-f",
help="Skip completeness verification (NOT RECOMMENDED: may result in incomplete requirements)",
),
] = False,
language: Annotated[Language | None, typer.Option("--language", "-l", help="Language for output")] = None,
) -> None:
"""Analyze user input for completeness and generate requirements prompt."""
if force:
print_info("Generating requirements prompt...")
else:
print_info("Analyzing input completeness...")
asyncio.run(_prompt_requirements_async(userinput, force, language))
# Async implementation
async def _validate_requirements_async(requirementspath: Path) -> None:
"""Async implementation for requirements validation."""
specs = await SpecDocuments.from_paths(requirements_path=requirementspath) # Auto-detect language
await run_handler_typed(handle_validate_requirements, specs)
@app.command()
def validate_requirements(
requirementspath: Annotated[Path, typer.Argument(help="Path to the requirements.md file to validate")] = Path(
"specs/requirements.md"
),
) -> None:
"""Validate requirements.md and generate fix instructions if needed."""
print_info(f"Validating {requirementspath}...")
asyncio.run(_validate_requirements_async(requirementspath))
# Async implementation
async def _prompt_design_async(requirementspath: Path) -> None:
"""Async implementation for design prompt generation."""
specs = await SpecDocuments.from_paths(requirements_path=requirementspath, language=Language.JAPANESE)
await run_handler_typed(handle_prompt_design, specs)
@app.command()
def prompt_design(
requirementspath: Annotated[
Path, typer.Argument(help="Path to the requirements.md file to generate design from")
] = Path("specs/requirements.md"),
) -> None:
"""Generate prompt for creating design.md from requirements.md."""
print_info("Generating design prompt...")
asyncio.run(_prompt_design_async(requirementspath))
# Async implementation
async def _validate_design_async(designpath: Path, requirementspath: Path | None) -> None:
"""Async implementation for design validation."""
specs = await SpecDocuments.from_paths(requirements_path=requirementspath, design_path=designpath)
await run_handler_typed(handle_validate_design, specs)
@app.command()
def validate_design(
designpath: Annotated[Path, typer.Argument(help="Path to the design.md file to validate")] = Path(
"specs/design.md"
),
requirementspath: Annotated[
Path | None,
typer.Option("--requirementsPath", "-r", help="Path to requirements.md file for traceability validation"),
] = None,
) -> None:
"""Validate design.md structure and traceability, generate fix instructions if needed."""
print_info(f"Validating {designpath}...")
asyncio.run(_validate_design_async(designpath, requirementspath))
# Async implementation
async def _prompt_tasks_async(designpath: Path, requirementspath: Path | None) -> None:
"""Async implementation for tasks prompt generation."""
specs = await SpecDocuments.from_paths(requirements_path=requirementspath, design_path=designpath)
await run_handler_typed(handle_prompt_tasks, specs)
@app.command()
def prompt_tasks(
designpath: Annotated[
Path,
typer.Argument(
help="Path to design.md file - tasks will enforce implementation of all components defined here"
),
] = Path("specs/design.md"),
requirementspath: Annotated[
Path | None,
typer.Option(
"--requirementsPath",
"-r",
help="Path to requirements.md file - tasks will ensure all REQ-XX are implemented",
),
] = None,
) -> None:
"""Generate prompt to create tasks.md (WBS) that defines mandatory implementation steps from design.md."""
print_info("Generating tasks prompt...")
asyncio.run(_prompt_tasks_async(designpath, requirementspath))
# Async implementation
async def _validate_tasks_async(taskspath: Path) -> None:
"""Async implementation for tasks validation."""
specs = await SpecDocuments.from_paths(tasks_path=taskspath, language=Language.JAPANESE)
await run_handler_typed(handle_validate_tasks, specs)
@app.command()
def validate_tasks(
taskspath: Annotated[
Path, typer.Argument(help="Path to tasks.md file to validate - ensures proper spec compliance enforcement")
] = Path("specs/tasks.md"),
) -> None:
"""Validate tasks.md structure, dependencies, and spec compliance requirements."""
print_info(f"Validating {taskspath}...")
asyncio.run(_validate_tasks_async(taskspath))
# Async implementation
async def _prompt_code_async(taskspath: Path, requirementspath: Path | None, designpath: Path | None) -> None:
"""Async implementation for code prompt generation."""
specs = await SpecDocuments.from_paths(
requirements_path=requirementspath, design_path=designpath, tasks_path=taskspath
)
await run_handler_typed(handle_prompt_code, specs)
@app.command()
def prompt_code(
taskspath: Annotated[
Path, typer.Argument(help="Path to tasks.md file - implementation must strictly follow these defined tasks")
] = Path("specs/tasks.md"),
requirementspath: Annotated[
Path | None,
typer.Option(
"--requirementsPath",
"-r",
help="Path to requirements.md file - all REQ-XX must be satisfied in implementation",
),
] = None,
designpath: Annotated[
Path | None,
typer.Option(
"--designPath",
"-d",
help="Path to design.md file - architecture and components must be implemented as specified",
),
] = None,
) -> None:
"""Generate essential implementation guidelines that enforce strict adherence to generated specs
(requirements→design→tasks)."""
print_info("Generating implementation prompt...")
asyncio.run(_prompt_code_async(taskspath, requirementspath, designpath))
# Async implementation
async def _analyze_changes_async(changedfile: Path, changedescription: str) -> None:
"""Async implementation for change analysis."""
determined_language = await _determine_language_for_file(None, str(changedfile))
await run_handler_typed(
handle_analyze_changes,
changedfile,
changedescription,
determined_language,
)
@app.command()
def analyze_changes(
changedfile: Annotated[
Path, typer.Option("--changedFile", "-f", help="Path to the specification file that was modified")
],
changedescription: Annotated[
str, typer.Option("--changeDescription", "-c", help="Detailed description of the changes made to the file")
],
) -> None:
"""Analyze changes to specs and generate prompts for dependent modifications."""
print_info(f"Analyzing changes to {changedfile}...")
asyncio.run(_analyze_changes_async(changedfile, changedescription))
# Async implementation
async def _get_traceability_async(requirementspath: Path, designpath: Path | None, taskspath: Path | None) -> None:
"""Async implementation for traceability report generation."""
specs = await SpecDocuments.from_paths(
requirements_path=requirementspath, design_path=designpath, tasks_path=taskspath
)
await run_handler_typed(handle_get_traceability, specs)
@app.command()
def get_traceability(
requirementspath: Annotated[
Path, typer.Argument(help="Path to the requirements.md file for traceability analysis")
] = Path("specs/requirements.md"),
designpath: Annotated[
Path | None, typer.Option("--designPath", "-d", help="Path to design.md file for component mapping")
] = None,
taskspath: Annotated[
Path | None, typer.Option("--tasksPath", "-t", help="Path to tasks.md file for task mapping")
] = None,
) -> None:
"""Generate current traceability report showing REQ↔DESIGN↔TASK mappings."""
print_info("Generating traceability report...")
asyncio.run(_get_traceability_async(requirementspath, designpath, taskspath))
# Async implementation
async def _generate_review_prompt_async(
task_id: str, taskspath: Path, requirementspath: Path | None, designpath: Path | None
) -> None:
"""Async implementation for review prompt generation."""
specs = await SpecDocuments.from_paths(
requirements_path=requirementspath, design_path=designpath, tasks_path=taskspath
)
await run_handler_typed(handle_generate_review_prompt, task_id, specs)
@app.command()
def generate_review_prompt(
task_id: Annotated[str, typer.Argument(help="Task ID to review for spec compliance (format: TASK-XX-XX)")],
taskspath: Annotated[
Path, typer.Argument(help="Path to tasks.md file - implementation must match this task definition exactly")
] = Path("specs/tasks.md"),
requirementspath: Annotated[
Path | None,
typer.Option(
"--requirementsPath",
"-r",
help="Path to requirements.md file - implementation must satisfy all related REQ-XX",
),
] = None,
designpath: Annotated[
Path | None,
typer.Option(
"--designPath", "-d", help="Path to design.md file - implementation must follow specified architecture"
),
] = None,
) -> None:
"""Generate implementation review prompt for specific TASK-ID to validate strict spec compliance and quality."""
print_info(f"Generating review prompt for {task_id}...")
asyncio.run(_generate_review_prompt_async(task_id, taskspath, requirementspath, designpath))
def version_callback(value: bool) -> None:
"""Show version and exit."""
if value:
try:
version = metadata.version("wassden")
except metadata.PackageNotFoundError:
version = "unknown"
typer.echo(f"wassden version {version}")
raise typer.Exit
@app.callback()
def main(
version: Annotated[
bool, typer.Option("--version", callback=version_callback, help="Show version and exit")
] = False,
) -> None:
"""wassden - MCP-based Spec-Driven Development toolkit."""