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

Tool Definition Quality

Score is being calculated. Check back soon.

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