permutations
Calculate the number of ordered arrangements of k items chosen from n distinct items without repetition.
Instructions
Calculate the number of ways to choose k items from n items without repetition and with order.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| n | Yes | The number of items to choose from. | |
| k | No | The optional number of items to choose. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/pymcp/server.py:221-254 (handler)The 'permutations' handler function. Calculates the number of ways to choose k items from n items without repetition and with order (P(n,k)). Uses math.perm(n,k). If k is None, defaults to n (full permutation). Raises McpError if k > n.
async def permutations( self, ctx: Context, n: Annotated[ int, Field( ge=1, description="The number of items to choose from.", ), ], k: Annotated[ int | None, Field( default=None, ge=1, description="The optional number of items to choose.", ), ], ) -> int: """Calculate the number of ways to choose k items from n items without repetition and with order.""" """If k is not provided, it defaults to n.""" assert isinstance(n, int) and n >= 1, "n must be a positive integer." if k is None: k = n if k > n: raise McpError( error=ErrorData( code=INVALID_PARAMS, message=f"k ({k}) cannot be greater than n ({n}).", ) ) return math.perm(n, k) - src/pymcp/server.py:67-71 (registration)Registration metadata for the 'permutations' tool in the PyMCP class. Declares the function name, tags (math, permutation, example), and annotations (readOnlyHint: True).
{ "fn": "permutations", "tags": ["math", "permutation", "example"], "annotations": {"readOnlyHint": True}, }, - src/pymcp/server.py:439-443 (registration)The 'permutations' tool is included in the response caching middleware's deterministic tools list, alongside 'greet'. This means its results are cached.
call_tool_settings=CallToolSettings( included_tools=["greet", "permutations"], ttl=EnvVars.RESPONSE_CACHE_TTL, enabled=EnvVars.RESPONSE_CACHE_TTL > 0, ), - src/pymcp/mixin.py:24-71 (helper)The MCPMixin.register_features method iterates over the 'tools' list and registers each function (including 'permutations') with the FastMCP instance using the fn name and metadata.
def register_features(self, mcp: FastMCP) -> FastMCP: """Register tools, resources, and prompts with the given FastMCP instance. Args: mcp (FastMCP): The FastMCP instance to register features with. Returns: FastMCP: The FastMCP instance with registered features. """ # Register tools for tool in self.tools: assert "fn" in tool, "Tool metadata must include the 'fn' key." tool_copy = copy.deepcopy(tool) fn_name = tool_copy.pop("fn") fn = getattr(self, fn_name) mcp.tool(**tool_copy)(fn) # pass remaining metadata as kwargs # Register resources for res in self.resources: assert "fn" in res and "uri" in res, "Resource metadata must include 'fn' and 'uri' keys." res_copy = copy.deepcopy(res) fn_name = res_copy.pop("fn") uri = res_copy.pop("uri") fn = getattr(self, fn_name) mcp.resource(uri, **res_copy)(fn) # Register prompts for pr in self.prompts: assert "fn" in pr, "Prompt metadata must include the 'fn' key." pr_copy = copy.deepcopy(pr) fn_name = pr_copy.pop("fn") fn = getattr(self, fn_name) mcp.prompt(**pr_copy)(fn) return mcp def get_tool_result(self, result: Any, metadata: dict[str, Any] | None = None) -> ToolResult: # pragma: no cover """Create a ToolResult object with the given result and metadata, including package metadata. Args: result (Any): The result to include in the ToolResult. metadata (Dict[str, Any] | None, optional): Additional metadata to include. Defaults to None. Returns: ToolResult: The ToolResult object containing the result and metadata. """ return ToolResult( structured_content={"result": result} if not isinstance(result, dict) else result, meta=metadata if metadata is not None else None, ) - The Base64EncodedBinaryDataResponse model used for binary data responses (not directly used by permutations, but part of the server response infrastructure). The permutations tool returns a plain int directly, not using this model.
class Base64EncodedBinaryDataResponse(BaseModel): """A base64 encoded binary data for MCP response along with its cryptographic hash.""" AVAILABLE_HASH_ALGORITHMS: ClassVar[list[str]] = list(hashlib.algorithms_available) AVAILABLE_HASH_ALGORITHMS_STR: ClassVar[str] = "" if not AVAILABLE_HASH_ALGORITHMS: # pragma: no cover pass elif len(AVAILABLE_HASH_ALGORITHMS) == 1: # pragma: no cover AVAILABLE_HASH_ALGORITHMS_STR = AVAILABLE_HASH_ALGORITHMS[0] elif len(AVAILABLE_HASH_ALGORITHMS) == 2: # pragma: no cover AVAILABLE_HASH_ALGORITHMS_STR = " and ".join(AVAILABLE_HASH_ALGORITHMS) else: AVAILABLE_HASH_ALGORITHMS_STR = ( ", ".join(AVAILABLE_HASH_ALGORITHMS[:-1]) + f", and {AVAILABLE_HASH_ALGORITHMS[-1]}" ) # See https://docs.python.org/3/library/hashlib.html#shake-variable-length-digests SHAKE_DIGEST_LENGTH: ClassVar[int] = 32 # bytes data: Base64Bytes = Field( description="Base64 encoded binary data.", ) hash: str = Field( description="A hexadecimal encoded of a hash of the binary data.", ) hash_algorithm: str = Field( description=f"The algorithm used to compute the hash, e.g., 'sha3_512'. Available algorithms: {AVAILABLE_HASH_ALGORITHMS_STR}", ) @model_validator(mode="after") def check_data_hash(self) -> "Base64EncodedBinaryDataResponse": if self.hash_algorithm not in Base64EncodedBinaryDataResponse.AVAILABLE_HASH_ALGORITHMS: raise ValueError( f"Unsupported hash algorithm: {self.hash_algorithm}. Available algorithms: {Base64EncodedBinaryDataResponse.AVAILABLE_HASH_ALGORITHMS_STR}" ) hasher = hashlib.new(self.hash_algorithm) hasher.update(self.data) computed_hash = ( hasher.hexdigest() if not self.hash_algorithm.startswith("shake") # Make sure that for variable length hash algorithms, such as SHAKE128 and SHAKE256, we get a fixed length hash for testing else hasher.hexdigest(Base64EncodedBinaryDataResponse.SHAKE_DIGEST_LENGTH) # ty: ignore[too-many-positional-arguments] ) if computed_hash != self.hash: raise ValueError(f"Hash mismatch: provided hash {self.hash} does not match computed hash {computed_hash}.") return self