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

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

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