Tool Schema Builder — Kotlin source
Build function-calling and MCP tool schemas that pass strict mode on the first try, and lint pasted ones against the strict-mode contract — additionalProperties, required-sync, defaults, enums — with one-click autofix for every mechanical violation. Runs entirely in your browser.
This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Tool Schema Builder — strict-mode validation of function-calling / MCP
// tool definitions (OpenAI strict mode / MCP inputSchema contract).
//
// Language: Kotlin (2.0, standard library only)
// Source: CosmoDev polyglot showcase port, from src/lib/tool-schema.ts
// (the canonical TypeScript implementation). Kotlin has no
// stdlib JSON, so the definition arrives as the Map<String, Any?>
// a kotlinx.serialization parse would produce; only the parse
// differs.
@Suppress("UNCHECKED_CAST")
fun obj(v: Any?): Map<String, Any?>? = v as? Map<String, Any?>
val SUPPORTED = setOf("string", "number", "integer", "boolean", "object", "array")
// The recursive strict-mode walk — every object nests the same rules.
fun checkObject(path: String, o: Map<String, Any?>, issues: MutableList<Pair<String, String>>) {
if (o["additionalProperties"] != false) issues += "no-additional-properties" to path
val props = obj(o["properties"]) ?: return
val required = o["required"] as? List<String> ?: emptyList()
val missing = props.keys.filter { it !in required }
if (missing.isNotEmpty()) issues += "all-required" to "$path: required missing ${missing.joinToString(", ")}"
for ((key, raw) in props) {
val prop = obj(raw) ?: continue
val p = "$path.properties.$key"
if ("default" in prop) issues += "no-defaults" to p
val desc = prop["description"] as? String ?: ""
if (desc.isBlank()) issues += "description-present" to p
val ty = prop["type"] as? String
if (ty == null || ty !in SUPPORTED) {
issues += "typed-properties" to "$p: must be one of ${SUPPORTED.joinToString(" | ")}"
}
(prop["enum"] as? List<Any?>)?.let { en ->
val kinds = en.map { it?.javaClass?.simpleName ?: "null" }.toSet()
val primitive = kinds.all { it in setOf("String", "Integer", "Long", "Double", "Boolean") }
if (en.isEmpty() || kinds.size > 1 || !primitive) issues += "enum-values" to p
}
if (ty == "array" && obj(prop["items"]) == null) issues += "array-items" to p
if (ty == "object" && obj(prop["properties"]) != null) checkObject(p, prop, issues)
}
}
// Validate a parsed tool definition ({name, description, input_schema}).
fun validate(root: Map<String, Any?>): List<Pair<String, String>> {
val issues = mutableListOf<Pair<String, String>>()
val name = root["name"] as? String ?: ""
if (name.isEmpty() || name.length > 64 || name.any { it !in 'a'..'z' && it !in '0'..'9' && it != '_' && it != '-' }) {
issues += "non-empty-name" to "name: must be 1-64 chars of [a-z0-9_-]"
}
if ((root["description"] as? String ?: "").isBlank()) {
issues += "description-present" to "description: the tool needs a description"
}
val schema = obj(root["input_schema"])
if (schema != null && schema["type"] == "object") {
checkObject("input_schema", schema, issues)
} else {
issues += "json-parseable" to "input_schema: must be an object with type: \"object\""
}
return issues
}
// Demo: validate a small broken definition and print the issues.
fun main() {
val broken = mapOf(
"name" to "Get_Weather",
"input_schema" to mapOf(
"type" to "object",
"properties" to mapOf(
"city" to mapOf("type" to "string", "default" to "Paris"),
"unit" to mapOf("type" to "string", "description" to "celsius or fahrenheit",
"enum" to listOf("c", 2)),
"tags" to mapOf("type" to "array")),
"required" to listOf("city")))
validate(broken).forEach { (rule, where) -> println("$rule $where") }
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →