# `BB.MCP.PeriSchema`
[🔗](https://github.com/beam-bots/bb_mcp/blob/main/lib/bb/mcp/peri_schema.ex#L5)

Converts BB command argument definitions into Peri schemas suitable for
`Anubis.Server.Frame.register_tool/3`.

Anubis takes the Peri schema we hand it, validates incoming tool calls
against it, and re-renders it as JSON Schema on the wire. So one
conversion suffices for both validation and discovery.

BB types map to Peri types as follows:

    :integer | :pos_integer | :non_neg_integer -> :integer
    :float | :number                           -> :float
    :string                                    -> :string
    :boolean                                   -> :boolean
    :atom                                      -> :atom
    :map | {:map, fields}                     -> :map
    :keyword_list                              -> :keyword
    :any                                       -> :any
    module / unknown                           -> :any

# `flatten_nested_params`

```elixir
@spec flatten_nested_params(BB.Dsl.Command.t(), map()) :: map()
```

Pre-process incoming params before Peri validation.

Some MCP clients interpret a flat property name like `"target.x"` as a nested
path and send `%{"target" => %{"x" => ...}}` instead of `%{"target.x" => ...}`.
We advertise the flat (dotted) form deliberately — nested object schemas are
poorly supported by some clients — so we flatten any nested object back to
the dotted form here before Peri's schema validator sees them.

Also coerces JSON whole numbers (`0`, `1`) to floats for fields whose schema
type is `:float` — JSON has no syntactic distinction between integer and
float zero, but Peri's `:float` validator rejects integers.

# `for_argument`

```elixir
@spec for_argument(BB.Dsl.Command.Argument.t()) :: term()
```

Build a Peri field value for a single argument, applying `required` and
`default` wrappers and a `description` meta tag where set.

# `for_command`

```elixir
@spec for_command(BB.Dsl.Command.t()) :: map()
```

Build a Peri schema (map keyed by argument name) for a command's arguments.

# `to_goal`

```elixir
@spec to_goal(BB.Dsl.Command.t(), map()) :: map()
```

Translate MCP-visible command params back to the command's original BB goal.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
