Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .jp/mcp/tools/rust/crate_readme.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@ inline_results = "off"
parameters = "function_call"

[conversation.tools.rust_crate_readme.parameters.crate_name]
type = "string"
required = true
summary = "Exact name of the crate."

[conversation.tools.rust_crate_readme.parameters.crate_version]
type = "string"
default = "latest"
summary = "Semver-compatible version, or `latest` for the most recent release."
2 changes: 0 additions & 2 deletions .jp/mcp/tools/rust/crate_resource.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,4 @@ inline_results = "off"
parameters = "function_call"

[conversation.tools.rust_crate_resource.parameters.uri]
type = "string"
required = true
summary = "Crate resource URI."
14 changes: 1 addition & 13 deletions .jp/mcp/tools/rust/crate_search_items.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,13 @@ inline_results = "off"
parameters = "function_call"

[conversation.tools.rust_crate_search_items.parameters.crate_name]
type = "string"
required = true
summary = "Exact name of the crate."

[conversation.tools.rust_crate_search_items.parameters.crate_version]
type = "string"
default = "latest"
summary = "Semver-compatible version, or `latest` for the most recent release."

[conversation.tools.rust_crate_search_items.parameters.query]
type = "string"
required = true
summary = "Partial-match query against item names and paths."

[conversation.tools.rust_crate_search_items.parameters.kinds]
type = "array"
summary = """\
Filter by item kind. Allowed values: Constant, Derive, Enum, Function, Macro, \
Method, Module, Struct, Trait, Type, Variant, Attribute. Omit for all kinds.\
"""
[conversation.tools.rust_crate_search_items.parameters.kinds.items]
type = "string"
summary = "Filter by item kind. Omit for all kinds."
2 changes: 0 additions & 2 deletions .jp/mcp/tools/rust/crate_versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,4 @@ inline_results = "off"
parameters = "function_call"

[conversation.tools.rust_crate_versions.parameters.crate_name]
type = "string"
required = true
summary = "Exact name of the crate."
2 changes: 0 additions & 2 deletions .jp/mcp/tools/rust/crates_search.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,4 @@ inline_results = "off"
parameters = "function_call"

[conversation.tools.rust_crates_search.parameters.query]
type = "string"
required = true
summary = "Search query."
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/jp_cli/src/cmd/query/tool/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ pub fn describe_tools() -> PartialToolConfig {
"Get detailed descriptions and usage examples for one or more tools.".to_owned(),
),
parameters: IndexMap::from([("tools".to_owned(), PartialToolParameterConfig {
kind: PartialOneOrManyTypes::One("array".to_owned()),
kind: Some(PartialOneOrManyTypes::One("array".to_owned())),
required: Some(true),
description: Some("Array of tool names to describe".to_owned()),
items: Some(Box::new(PartialToolParameterConfig {
kind: PartialOneOrManyTypes::One("string".to_owned()),
kind: Some(PartialOneOrManyTypes::One("string".to_owned())),
..Default::default()
})),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion crates/jp_cli/src/cmd/query/tool/inquiry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ fn estimate_fixed_overhead_chars(
chars += desc.len();
}
// Parameter schemas are serialized as JSON by providers.
chars += serde_json::to_string(&tool.to_parameters_schema()).map_or(0, |s| s.len());
chars += serde_json::to_string(&tool.parameters).map_or(0, |s| s.len());
}

chars
Expand Down
4 changes: 2 additions & 2 deletions crates/jp_cli/src/cmd/query/tool/inquiry_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ fn overhead_tool_definitions() {
summary: Some("Search files.".to_string()),
..Default::default()
},
parameters: IndexMap::new(),
parameters: serde_json::json!({ "type": "object", "properties": {} }),
};
let result = estimate_fixed_overhead_chars(None, &[], &[], &[tool]);
// name + description + serialized schema
Expand All @@ -472,7 +472,7 @@ fn overhead_combines_all_sources() {
let tool = ToolDefinition {
name: "t".to_string(),
docs: ToolDocs::default(),
parameters: IndexMap::new(),
parameters: serde_json::json!({ "type": "object", "properties": {} }),
};

let combined = estimate_fixed_overhead_chars(
Expand Down
141 changes: 46 additions & 95 deletions crates/jp_config/src/conversation/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use std::{fmt, str::FromStr};
use indexmap::IndexMap;
use schematic::{Config, ConfigEnum, ConfigError, HandlerError, PartialConfig as _};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use tracing::warn;
use serde_json::Value;

pub use crate::types::command::{
CommandConfig, CommandConfigOrString, PartialCommandConfig, PartialCommandConfigOrString,
Expand Down Expand Up @@ -595,34 +594,41 @@ impl ToPartial for ToolConfig {
}
}

/// Tool parameter configuration.
/// Overrides for one tool parameter.
///
/// This type doubles as a recursive JSON Schema node: `items` points to the
/// schema for array elements, `properties` describes the fields of object
/// elements.
/// MCP parameters inherit unset fields from the server's schema.
/// Local and built-in parameters must set `type` and define complete array and
/// object shapes.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Config)]
#[config(rename_all = "snake_case")]
pub struct ToolParameterConfig {
/// The type of the parameter.
///
/// MCP tools inherit this from the server.
/// Local and built-in tools must set it explicitly.
#[setting(nested)]
#[serde(rename = "type")]
pub kind: OneOrManyTypes,
#[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
pub kind: Option<OneOrManyTypes>,

/// The default value of the parameter.
/// The value used when the argument is omitted.
///
/// MCP tools inherit the server's default when unset.
/// Local and built-in tools have no default when unset.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default: Option<Value>,

/// Whether the parameter is required.
#[setting(default)]
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub required: bool,
///
/// MCP parameters inherit the server's requirement when unset.
/// Local and built-in parameters default to optional.
/// A requirement declared by an MCP server cannot be removed.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,

/// A short summary of the parameter.
///
/// This is included in the JSON schema sent to the LLM.
/// If not set, falls back to [`description`].
///
/// [`description`]: Self::description
/// This is included in the schema sent to the LLM.
/// When unset, `description` is used.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,

Expand All @@ -638,17 +644,32 @@ pub struct ToolParameterConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub examples: Option<String>,

/// A list of possible values for the parameter.
#[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
pub enumeration: Vec<Value>,
/// Values accepted by the parameter.
///
/// MCP tools inherit the server's enum when unset.
/// Local and built-in tools accept any value matching `type` when unset.
/// Set this to an empty array to remove an inherited enum.
///
/// For array elements, set `items.enum`.
/// An enum on the array itself must contain complete arrays, for example
/// `enum = [["one"], ["two", "three"]]`.
#[serde(default, rename = "enum", skip_serializing_if = "Option::is_none")]
pub enumeration: Option<Vec<Value>>,

/// Schema for array items (recursive).
/// Constraints applied to every array element.
///
/// MCP tools inherit `items` when unset.
/// Local and built-in array parameters must define it.
#[setting(nested)]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[expect(clippy::use_self, reason = "macro can't resolve `Self`")]
pub items: Option<Box<ToolParameterConfig>>,

/// Sub-properties for object-typed parameters (recursive).
/// Constraints for fields of an object parameter.
///
/// MCP properties are merged by name.
/// Entries here may narrow nested fields or add fields to local and
/// built-in object parameters.
#[setting(nested, merge = merge_nested_indexmap)]
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
#[expect(clippy::use_self, reason = "macro can't resolve `Self`")]
Expand All @@ -658,7 +679,7 @@ pub struct ToolParameterConfig {
impl PartialConfigDelta for PartialToolParameterConfig {
fn delta(&self, next: Self) -> Self {
Self {
kind: self.kind.delta(next.kind),
kind: delta_opt_partial(self.kind.as_ref(), next.kind),
default: delta_opt(self.default.as_ref(), next.default),
required: delta_opt(self.required.as_ref(), next.required),
summary: delta_opt(self.summary.as_ref(), next.summary),
Expand Down Expand Up @@ -690,17 +711,13 @@ impl ToPartial for ToolParameterConfig {
let defaults = Self::Partial::default();

Self::Partial {
kind: self.kind.to_partial(),
kind: self.kind.as_ref().map(ToPartial::to_partial),
default: partial_opts(self.default.as_ref(), defaults.default),
required: partial_opt(&self.required, defaults.required),
required: partial_opts(self.required.as_ref(), defaults.required),
summary: partial_opts(self.summary.as_ref(), defaults.summary),
description: partial_opts(self.description.as_ref(), defaults.description),
examples: partial_opts(self.examples.as_ref(), defaults.examples),
enumeration: if self.enumeration.is_empty() {
None
} else {
Some(self.enumeration.clone())
},
enumeration: self.enumeration.clone(),
items: self.items.as_ref().map(|v| Box::new(v.to_partial())),
properties: self
.properties
Expand Down Expand Up @@ -762,72 +779,6 @@ impl OneOrManyTypes {
Self::Many(v) => v.iter().any(|v| v == type_),
}
}

/// Return whether the type is exactly the given type.
#[must_use]
pub fn is_type(&self, type_: &str) -> bool {
match self {
Self::One(v) => v.as_str() == type_,
Self::Many(v) => v.len() == 1 && v[0] == type_,
}
}
}

impl ToolParameterConfig {
/// Return whether the parameter is required.
#[must_use]
pub const fn is_required(&self) -> bool {
self.required
}

/// Convert the parameter to a JSON schema.
pub fn to_json_schema(&self) -> Value {
let mut map = Map::new();
map.insert("type".to_owned(), match &self.kind {
OneOrManyTypes::One(v) => v.clone().into(),
OneOrManyTypes::Many(v) => v.clone().into(),
});

if let Some(desc) = self.summary.as_deref().or(self.description.as_deref()) {
map.insert("description".to_owned(), desc.into());
}

if let Some(default) = self.default.clone() {
map.insert("default".to_owned(), default);
}

if !self.enumeration.is_empty() {
map.insert("enum".to_owned(), self.enumeration.as_slice().into());
}

if let Some(items) = self.items.as_ref() {
if !self.kind.is_type("array") {
warn!("Unexpected `items` property for non-array type");
}
map.insert("items".to_owned(), items.to_json_schema());
}

if !self.properties.is_empty() {
let props: Map<_, _> = self
.properties
.iter()
.map(|(k, v)| (k.clone(), v.to_json_schema()))
.collect();
map.insert("properties".to_owned(), Value::Object(props));

let required: Vec<_> = self
.properties
.iter()
.filter(|(_, v)| v.required)
.map(|(k, _)| Value::String(k.clone()))
.collect();
if !required.is_empty() {
map.insert("required".to_owned(), Value::Array(required));
}
}

Value::Object(map)
}
}

/// The source of a tool.
Expand Down
Loading
Loading