Skip to main content
Glama
watamoo

Crossword MCP Server

by watamoo

setup

Initialize a crossword puzzle by loading grid layout and clue definitions to prepare for solving. Accepts grid text with cell symbols and JSON-formatted clues for across and down directions.

Instructions

クロスワードの盤面とカギ定義を読み込み、状態を初期化する。

Args: grid_text (str): 行番号つきの盤面テキスト。列・行番号は全角数字で表記し、 文字が入るマスは "?"、黒マスは "#" で記述する。各行のマス数が一致している 必要がある。 clue_text (str): JSON Lines 形式のカギ定義。各行は id/direction/row/col /length/clue を持つ辞書で、direction は "across" か "down"。rowcol は 1 起点の正整数。

Returns: list[list[str]]: 正規化済みセル行列。各要素は "?" または "#" のシンボル。

Raises: ValueError: 盤面の行長不一致・未知のセル記号・カギ定義の欠損や不正値など、 入力内容が検証に失敗した場合。

Notes: この関数を呼び出すと既存の候補リストは破棄され、状態が再初期化される。

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
grid_textYes
clue_textYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The primary handler for the MCP 'setup' tool. Decorated with @mcp.tool() for registration. Parses input grid_text and clue_text using helpers, initializes the global PuzzleState, clears candidates, and returns the loaded grid.
    @mcp.tool()
    async def setup(grid_text: str, clue_text: str) -> list[list[str]]:
        """クロスワードの盤面とカギ定義を読み込み、状態を初期化する。
    
        Args:
            grid_text (str): 行番号つきの盤面テキスト。列・行番号は全角数字で表記し、
                文字が入るマスは "?"、黒マスは "#" で記述する。各行のマス数が一致している
                必要がある。
            clue_text (str): JSON Lines 形式のカギ定義。各行は `id`/`direction`/`row`/`col`
                /`length`/`clue` を持つ辞書で、`direction` は "across" か "down"。`row` と
                `col` は 1 起点の正整数。
    
        Returns:
            list[list[str]]: 正規化済みセル行列。各要素は "?" または "#" のシンボル。
    
        Raises:
            ValueError: 盤面の行長不一致・未知のセル記号・カギ定義の欠損や不正値など、
                入力内容が検証に失敗した場合。
    
        Notes:
            この関数を呼び出すと既存の候補リストは破棄され、状態が再初期化される。
        """
    
        grid = _load_grid(grid_text)
        clues = _load_clues(clue_text, grid)
    
        state.grid = grid
        state.clues = clues
        state.candidates.clear()
    
        return grid
  • Helper function called by setup to parse and validate the grid_text into a 2D list of fillable ('?') and block ('#') cells, handling full-width numbers and row consistency.
    def _load_grid(grid_text: str) -> list[list[str]]:
        "グリッド文字列を読み込み、全角表記の入力からセル行列を構築する"
        grid: list[list[str]] = []
    
        for raw_line in grid_text.splitlines():
            if FILLABLE_CELL not in raw_line and BLOCK_CELL not in raw_line:
                continue
    
            normalized_line = raw_line.translate(_FULLWIDTH_DIGIT_TO_ASCII)
            parts = normalized_line.strip().split()
            if not parts:
                continue
    
            if parts[0].isdigit():
                tokens = parts[1:]
            else:
                tokens = parts
    
            if not tokens:
                continue
    
            for token in tokens:
                if token not in {FILLABLE_CELL, BLOCK_CELL}:
                    raise ValueError(f"未知のマス表現を検出しました: {token}")
            if grid and len(tokens) != len(grid[0]):
                raise ValueError("行ごとのマス数が一致しません。入力を確認してください。")
    
            grid.append(tokens)
    
        return grid
  • Helper function called by setup to parse JSON Lines clue_text into a dictionary of validated Clue objects, using _validate_clue_payload for each.
    def _load_clues(clue_text: str, grid: list[list[str]]) -> dict[str, Clue]:
        "JSON Lines 形式のカギ定義を読み込み、検証した Clue オブジェクトへ変換する"
    
        lines = [line.strip() for line in clue_text.splitlines() if line.strip()]
        if not lines:
            raise ValueError("カギ情報が存在しません。")
    
        seen_ids: set[str] = set()
        clues: dict[str, Clue] = {}
    
        for line_no, raw in enumerate(lines, start=1):
            try:
                payload = json.loads(raw)
            except json.JSONDecodeError as exc:
                raise ValueError(f"{line_no} 行目のカギ情報を JSON として読み込めません。") from exc
    
            if not isinstance(payload, dict):
                raise ValueError(f"{line_no} 行目のカギ情報が辞書形式ではありません。")
    
            clue = _validate_clue_payload(payload, grid, seen_ids)
            clues[clue.clue_id] = clue
    
        return clues
  • Helper function used by _load_clues to validate individual clue payloads, compute positions on grid, and create Clue dataclass instances.
    def _validate_clue_payload(
        payload: dict[str, object],
        grid: list[list[str]],
        seen_ids: set[str],
    ) -> Clue:
        "JSON 行から取得したカギ情報を検証し、内部で扱いやすい形へ変換する"
    
        required_keys = ("id", "direction", "row", "col", "length", "clue")
        for key in required_keys:
            if key not in payload:
                raise ValueError(f"カギ情報に {key} がありません。")
    
        clue_id = str(payload["id"]).strip()
        if not clue_id:
            raise ValueError("clue_id が空です。")
        if clue_id in seen_ids:
            raise ValueError(f"clue_id={clue_id} が重複しています。")
    
        direction = str(payload["direction"]).strip().lower()
        if direction not in {"across", "down"}:
            raise ValueError(f"clue_id={clue_id} の direction が不正です: {payload['direction']}")
    
        try:
            row = int(payload["row"])
            col = int(payload["col"])
            length = int(payload["length"])
        except (TypeError, ValueError) as exc:
            raise ValueError(f"clue_id={clue_id} の row/col/length を整数に変換できません。") from exc
    
        if row <= 0 or col <= 0 or length <= 0:
            raise ValueError(f"clue_id={clue_id} の row/col/length が不正です。")
    
        rows = len(grid)
        cols = len(grid[0]) if rows else 0
        start_r = row - 1
        start_c = col - 1
    
        if start_r >= rows or start_c >= cols:
            raise ValueError(f"clue_id={clue_id} の開始位置が盤面外です。")
    
        positions: list[tuple[int, int]] = []
        for offset in range(length):
            r = start_r + (offset if direction == "down" else 0)
            c = start_c + (offset if direction == "across" else 0)
            if r >= rows or c >= cols:
                raise ValueError(f"clue_id={clue_id} の単語が盤面外にはみ出します。")
            if grid[r][c] == BLOCK_CELL:
                raise ValueError(f"clue_id={clue_id} が黒マスを含んでいます。")
            positions.append((r, c))
    
        clue_text = str(payload["clue"]).strip()
        if not clue_text:
            raise ValueError(f"clue_id={clue_id} の clue が空です。")
    
        seen_ids.add(clue_id)
        return Clue(
            clue_id=clue_id,
            direction=direction,
            length=length,
            positions=tuple(positions),
            text=clue_text,
        )
  • Dataclass defining the structure for validated clues, used in PuzzleState by setup.
    @dataclass(frozen=True)
    class Clue:
        "探索ロジックが参照する検証済みのカギ定義"
    
        clue_id: str
        direction: str
        length: int
        positions: tuple[tuple[int, int], ...]
        text: str
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden and does well at disclosing key behavioral traits. It explicitly states that calling this tool 'discards existing candidate lists and reinitializes the state' (destructive behavior), describes the return format (normalized cell matrix), and documents error conditions (ValueError for validation failures). It doesn't mention performance characteristics or rate limits, but covers the essential behavioral aspects for a setup/initialization tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and appropriately sized. It starts with a clear purpose statement, then provides detailed parameter documentation in an Args section, followed by Returns and Raises sections, and ends with important Notes. Every sentence earns its place by providing essential information without redundancy. The information is front-loaded with the core purpose stated first.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given this is a setup/initialization tool with 2 parameters, no annotations, but with output schema (returns list[list[str]]), the description is complete. It explains what the tool does, how to use parameters, what it returns, what errors can occur, and important behavioral notes about state reinitialization. The output schema handles return type documentation, so the description appropriately focuses on semantics and behavior.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 0%, so the description must fully compensate - which it does excellently. It provides detailed semantic explanations for both parameters: grid_text format (row numbers, cell symbols, consistency requirements) and clue_text format (JSON Lines with specific fields, direction values, 1-based indexing). The description adds substantial meaning beyond what the bare schema provides, making parameter usage clear.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs ('読み込み' - load, '初期化する' - initialize) and resources ('クロスワードの盤面とカギ定義' - crossword grid and clue definitions). It distinguishes from siblings by explaining this tool sets up/initializes the puzzle state while other tools like get_candidates, register_candidates, render_solution, and search_consistent_sets operate on that initialized state.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context about when to use this tool: to initialize the crossword puzzle state by loading grid and clue data. It mentions that calling this function discards existing candidate lists and reinitializes the state, which implies this should be used at the start of puzzle solving. However, it doesn't explicitly state when NOT to use it or name specific alternatives among the sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/watamoo/mcp-crossword-tools'

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