Skip to content

feat(core): load the calling user's plugin settings for tools - #966

Merged
abdulrafey1 merged 7 commits into
mainfrom
rafe/lms-settings-loader
Oct 26, 2026
Merged

abdulrafey1 merged 7 commits into
mainfrom
rafe/lms-settings-loader

Conversation

@abdulrafey1

@abdulrafey1 abdulrafey1 commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

What

LMS tools take the user's credentials as model-written arguments (#855). This adds the shared loader
that lets a tool read the calling user's stored plugin settings on the server instead.

Changes

  • feat(core): load_user_plugin_config(plugin_name, config_class) in sparkth.lib.plugins reads the
    bound caller (current_user_id()) with one query for that plugin's row and returns their validated,
    typed config.
  • feat(core): PluginNotConfiguredError is raised when the user has saved no settings (no row or an
    empty config). Its message tells the model to send the user to the Settings page to add them.
  • feat(core): PluginConfigInvalidError is raised when the saved settings no longer validate. Its
    message asks the user to update them. Only the plugin, the user id and the failing field names are
    logged, never the values, and the validation error is not chained.
  • refactor(core): both messages read the page name from PLUGIN_SETTINGS_PAGE_NAME ("Settings").
  • docs: "Reading the caller's stored settings" in docs/guides/plugins.md.

How to Test

  1. uv run pytest tests/core/plugins/test_load_user_plugin_config.py -v: the caller's settings come back
    typed; another user's row, no row and an empty config raise PluginNotConfiguredError; an invalid
    config raises PluginConfigInvalidError; an unbound caller raises NoAuthenticatedUser.
  2. The invalid-config test asserts that neither the bad value nor the secret appears in the log.

Notes

No migration, no new env var. Nothing calls the loader yet; the next PRs in the stack use it.

This description was written with the assistance of an LLM (Claude).

🤖 Generated with Claude Code

Add load_user_plugin_config, which reads the bound user's stored settings
for a plugin and validates them into the plugin's config class. A user with
no usable settings gets PluginNotConfiguredError, whose message tells the
model to point them at the My Plugins page. Validation failures log only
the plugin, the user id and the failing field names.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@abdulrafey1
abdulrafey1 added this pull request to stack #971 October 21, 2026 23:51
@abdulrafey1 abdulrafey1 self-assigned this Oct 21, 2026

@hamza-56 hamza-56 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 point:

Comment thread sparkth/core/plugins/service.py Outdated
Comment thread sparkth/core/plugins/service.py Outdated
user_id = current_user_id()
async with session_scope() as session:
row = (await PluginService().get_user_plugin_map(session, user_id)).get(plugin_name)
if row is not None and row.config:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't check row.enabled, so if a user switches a plugin off, its stored credentials are still loaded. maybe add if row is not None and row.enabled and row.config:, plus a test for a disabled row.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chat never binds a disabled plugin's tools, and /ai/mcp has no bound user, so no caller can reach the loader for a disabled plugin.

Comment thread sparkth/core/plugins/service.py Outdated
Comment thread sparkth/core/plugins/service.py Outdated
Comment thread sparkth/core/plugins/service.py Outdated
Comment thread sparkth/core/plugins/exceptions.py Outdated
abdulrafey1 and others added 5 commits October 26, 2026 16:00
The loader fetched every plugin row for the user to use one. It now selects the single row by
plugin name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
model_validate reports a non-object stored config as a ValidationError, so the TypeError branch
that only **-unpacking could reach is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Settings that no longer validate raise PluginConfigInvalidError, which asks the user to update
them, instead of PluginNotConfiguredError, which asks them to add settings they already saved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both plugin settings errors read the page name from PLUGIN_SETTINGS_PAGE_NAME, now "Settings",
the name the page goes by.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@abdulrafey1
abdulrafey1 removed this pull request from stack #971 October 26, 2026 13:01
@abdulrafey1
abdulrafey1 added this pull request to stack #999 October 26, 2026 13:02
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@abdulrafey1
abdulrafey1 merged commit 21d70d9 into main Oct 26, 2026
10 checks passed
@abdulrafey1
abdulrafey1 deleted the rafe/lms-settings-loader branch October 26, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants