developmentseed / developmentseed/mcp-toolsets-runtime
Hard-coded @state example key in handle descriptions gets copied by models
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 39
Description
`_handle_branch()` in `mcp_state/handles.py` puts a hard-coded example key into
the schema description of every `NotAuthored` parameter:
```python
"A session-state reference, e.g. "
f"{handle_for('dataset-search/search_datasets/area_of_interest')} "
"— the key from a [state updated: …] note. …"
```
Models copy it. Driving `dss-agentic-ai-services` (runtime 0.8.1) with only the
`cds` toolset mounted — so no AOI tool was connected and no key could possibly
exist — the agent replied to the user with:
> I don't have a valid area defined yet, so I can't submit the request. Please
> provide an area of interest (as a bounding box or AOI key from a prior tool
> result) … or an AOI key you already have (e.g.
> `@state:dataset-search/search_datasets/area_of_interest`)
It never called the tool, so it never hit the guard — it just handed the user a
key that doesn't exist, naming a toolset (`dataset-search`) that isn't part of
that deployment at all. The example reads as a real, available key rather than
as a placeholder.
**The guard itself is fine.** If the model *does* call the tool with that key,
the refusal is good and says so plainly:
```
submit_request was not called. Unresolved session-state references:
area: @state:dataset-search/search_datasets/area_of_interest — no such key in
session state. Nothing has been published to session state yet.
```
So this is about the description a model reads while deciding, not about
enforcement.
**Suggestion.** Either drop the concrete example and describe the shape
(`@state://`), or build it from a key that is actually in
session state — the surrounding machinery already knows what has been published,
since `unresolved_message` lists real keys when it refuses. A placeholder that
can't be mistaken for a live key would also do.
Worth noting it affects every consumer, not just this one, and the cost is a
model confidently telling a user to use a key that was never going to work.
Contributor guide
Research direction
Start in mcp_state/handles.py at _handle_branch() and compare its description construction with unresolved_message, which already exposes published keys. Choose a non-live placeholder or shape description, then verify that generated NotAuthored parameter descriptions no longer present the concrete dataset-search key while existing unresolved-reference behavior remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 75/100