Skip to content

Commit 441ce3a

Browse files
committed
chore: harden MCP metadata and docs toolchain
1 parent 42d3d6d commit 441ce3a

4 files changed

Lines changed: 116 additions & 61 deletions

File tree

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- uses: actions/checkout@v6
2121
- uses: actions/setup-node@v6
2222
with:
23-
node-version: 20
23+
node-version: 22
2424
cache: npm
2525
cache-dependency-path: docs-site/package-lock.json
2626
- run: cd docs-site && npm ci

docs-site/package-lock.json

Lines changed: 39 additions & 59 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs-site/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"private": true,
55
"type": "module",
66
"description": "ThetaDataDx documentation site",
7+
"engines": {
8+
"node": ">=22"
9+
},
710
"scripts": {
811
"dev": "vitepress dev docs",
912
"build": "vitepress build docs",

tools/mcp/src/main.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ fn tool_definitions() -> Vec<Value> {
332332
&p.name,
333333
json!({
334334
"type": registry::param_type_to_json_type(p.param_type),
335-
"description": p.description,
335+
"description": mcp_param_description(ep, p),
336336
}),
337337
);
338338
if p.required && !required.contains(&p.name) {
@@ -390,6 +390,40 @@ fn tool_definitions() -> Vec<Value> {
390390
tools
391391
}
392392

393+
/// Return the LLM-facing MCP parameter description for a registry endpoint.
394+
///
395+
/// Most parameters can use the shared registry wording directly. A small set of
396+
/// option bulk-query parameters benefit from MCP-specific clarification because
397+
/// the MCP transport uses `"0"` as the wildcard sentinel instead of REST's
398+
/// `*`, and `strike_range` only filters an already-bulk selection.
399+
fn mcp_param_description(ep: &registry::EndpointMeta, param: &registry::ParamMeta) -> String {
400+
if ep.category == "option" {
401+
match param.name {
402+
"strike" => {
403+
return format!(
404+
"{}. Use \"0\" for wildcard/bulk strike selection on endpoints that support bulk option queries.",
405+
param.description
406+
);
407+
}
408+
"expiration" => {
409+
return format!(
410+
"{}. Use \"0\" for wildcard/bulk expiration selection on endpoints that support bulk option queries.",
411+
param.description
412+
);
413+
}
414+
"strike_range" => {
415+
return format!(
416+
"{}. Filters a wildcard/bulk option selection around spot/ATM; it does not expand a pinned strike.",
417+
param.description
418+
);
419+
}
420+
_ => {}
421+
}
422+
}
423+
424+
param.description.to_string()
425+
}
426+
393427
fn negotiate_protocol_version(client_version: Option<&str>) -> &'static str {
394428
client_version
395429
.and_then(|version| {
@@ -1204,6 +1238,44 @@ mod tests {
12041238
}
12051239
}
12061240

1241+
#[test]
1242+
fn tool_schemas_clarify_option_bulk_wildcards_for_llm_consumers() {
1243+
let tool = tool_definitions()
1244+
.into_iter()
1245+
.find(|tool| {
1246+
tool.get("name")
1247+
.and_then(|value: &Value| value.as_str())
1248+
== Some("option_history_greeks_eod")
1249+
})
1250+
.expect("option_history_greeks_eod tool should exist");
1251+
1252+
let strike = tool
1253+
.pointer(["inputSchema", "properties", "strike", "description"])
1254+
.and_then(|value| value.as_str())
1255+
.expect("strike description should exist");
1256+
let expiration = tool
1257+
.pointer(["inputSchema", "properties", "expiration", "description"])
1258+
.and_then(|value| value.as_str())
1259+
.expect("expiration description should exist");
1260+
let strike_range = tool
1261+
.pointer(["inputSchema", "properties", "strike_range", "description"])
1262+
.and_then(|value| value.as_str())
1263+
.expect("strike_range description should exist");
1264+
1265+
assert!(
1266+
strike.contains("\"0\" for wildcard/bulk strike selection"),
1267+
"strike description should explain MCP wildcard strike semantics: {strike}"
1268+
);
1269+
assert!(
1270+
expiration.contains("\"0\" for wildcard/bulk expiration selection"),
1271+
"expiration description should explain MCP wildcard expiration semantics: {expiration}"
1272+
);
1273+
assert!(
1274+
strike_range.contains("does not expand a pinned strike"),
1275+
"strike_range description should explain wildcard-only filtering semantics: {strike_range}"
1276+
);
1277+
}
1278+
12071279
#[test]
12081280
fn optional_i32_args_reject_out_of_range_values() {
12091281
let args = convert_endpoint_args(&sonic_rs::json!({

0 commit comments

Comments
 (0)