developmentseed / developmentseed/mcp-toolsets-runtime
A per-user credential the server holds still prompts every visitor, and an expired session reads as a broken chat
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 39
Description
`GET /connections` reports, for each declared credential header, whether the
server already holds a value, so a deployment with one shared key does not ask
every visitor to paste it. It is computed once, from the process environment:
```python
agent = built()
from_environment = resolve_credentials(agent.required, {})
...
{"header": header, "supplied": header in from_environment}
```
That is right for a shared key and wrong for a per-user one.
A deployment that federates identity — an OIDC proxy at the front door, the API
verifying the access token, and the user's own credential for the downstream
service fetched and held per `sub` — supplies `x-cds-token` for the signed-in
visitor and for nobody else. The environment holds nothing in either case, so
`supplied` is `false` for everyone, and every federated user is told to paste a
token the server is about to supply on their behalf.
This is the shape `dss-agentic-ai-services` is building toward. Everything below
the flag already fits: the resolution order, the ContextVar, the MCP transport
and the toolset's own header are untouched, and the model still never sees the
value. The one thing that does not fit is the field the client reads to decide
whether to ask.
**Suggestion.** Answer `/connections` per request rather than per process. A run
already resolves exactly this pair one route away:
```python
credentials = credentials_for(request.headers, agent.required)
```
so `resolve_credentials(agent.required, credentials_for(...))` would make
`supplied` mean "this deployment can supply it *for this caller*", which is what
a client is actually asking. The credential panel then needs no change at all:
it prompts the visitors who cannot be federated and stays quiet for the ones who
can, which is the fallback role it should have.
**A second, smaller thing in the same story.** The client has no handling for a
`401`. `readThread` and `readTurns` fall into `.catch(() => null)` and the page
quietly starts a fresh thread; a refused run lands in
```ts
} catch (error) {
agent.addMessage({ ... content: `client error: ${String(error)}` });
```
So an expired session reads as a broken chat rather than as a prompt to sign in
again. It is reachable rather than theoretical: an access token measured in
minutes against a turn that can stream for an hour means the session lapses
*between* fetches, not during one. A few lines would do it — on a `401`, reload,
and let whatever sits in front issue its own redirect.
Neither is specific to one consumer. Any deployment that authenticates its
visitors meets the first, and the second lands on anything that puts a session
in front of the page. Both are worth having before someone puts a proxy there,
since otherwise the first symptom of either is a bug report about the chat.
Contributor guide
Research direction
Trace the /connections route and the existing credentials_for(request.headers, agent.required) call first, then inspect the client paths named readThread, readTurns, and the agent error catch. Done means supplied reflects the current caller's credentials and a 401 leads to sign-in handling instead of a fresh thread or generic chat error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, typescript
- Domain
- api, authentication, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100