Sentry and Google Calendar plugins, user logins in chat - #736
Merged
Merged
Conversation
…chat Sentry and Google Calendar join the plugin catalog. OAuth discovery now reads RFC 9728 metadata at the endpoint's path, asks for the catalog's scopes and extra authorize params, and authenticates a deployment's own client with its secret, which Google needs. An agent config names user_plugins beside plugins. Each end user reaches those with their own login: the model gets <id>__list_tools and <id>__call_tool, and the first call without a login starts one and puts a plugin_authorization attachment on the reply. listConfigPlugins lists plugins the config names that the app has not connected as not_connected, for the dashboard to remind about. Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
agent.yaml declares user_plugins beside plugins and sync carries them. Accelerated takes the end user a conversation is for, and a plugin tool answering authorization_required becomes an authorization_required event with the URL to open. Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
…tachment Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
…ssages A model that calls a tool first answers with nothing but the call, and ask() ended there, closing the conversation before the tool came back. responded carries pending_work, and ask() follows on until a reply has nothing pending. A conversation the router keeps for an end user takes each message by a command id, so respond sends one when Accelerated acts for a user. Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
…s their login An MCP server answering 401 is ErrUnauthorized. A user plugin's tool that gets one, opening the session or mid-conversation, drops the refused login and answers authorization_required again, so the reply carries a fresh plugin_authorization attachment instead of an error. Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
The config CRUD and the plugin catalog, logins and authorize operations move from legacy.yaml to Huma, with AgentConfig, AgentConfigRequest and the plugin schemas as Go types documented by their tags, so user_plugins and not_connected no longer live in legacy.yaml. The rendered spec is the same apart from Huma's 400 and 500 on listPlugins. Huma reads a request before its handler runs, so a caller with no credential is now refused by middleware with the 401 the handlers gave, rather than told what is wrong with its body. Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
…o Go Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Sprint 2 of
.factory/features/mcp_plugins_and_connectors.md: an agent reads the company's Sentry (one login per app) and the end user's own Google Calendar (one login per user). Both are set up inagent.yaml. The dashboard reminds the app to finish the company login, and the user's login is asked for in the chat as a custom attachment. Following review: a login the provider later refuses is asked for again, and the config and plugin operations no longer live inlegacy.yaml(the rest of it is ported in the stacked #739).How it works
flowchart LR yaml["agent.yaml<br/>plugins: sentry<br/>user_plugins: google_calendar"] -->|syncAgent| config[(agent_configs<br/>plugins, user_plugins)] config -->|listConfigPlugins| dash["Dashboard<br/>sentry: not_connected"] dash -->|authorizePlugin, once| sentryLogin[(app login<br/>user_id empty)] config --> session[Session] sentryLogin -->|sentry__* tools| session session -->|"google_calendar__list_tools<br/>google_calendar__call_tool"| runner{caller has a login<br/>the provider accepts?} runner -->|no, or 401| oauth["start OAuth, store pending login<br/>for this user"] --> attach["reply carries<br/>plugin_authorization attachment"] runner -->|yes| mcp["MCP with the user's token"]sentry,google_calendarinplugins.yaml; discovery at the endpoint's path (RFC 9728 §3.1), catalogscopesandauthorize_params, client secret for the deployment's own clientcalendar.readonly, offline access and consentTestMetadataAtTheEndpointsPathIsFoundFirst,TestTheDeploymentsOwnClientSendsItsSecret, live runMcp-Session-Idkept after initializeTestTheSessionTheServerGivesIsSentBackuser_pluginson config, sync, patch, session spec;agent_plugin_connections.user_id(migration20261003200000)TestAnEndUsersLoginIsTheirsAloneAndSendsThemBackToTheConversationlistConfigPluginsadds named, unconnected app plugins asnot_connectedTestAPluginTheAgentNamesThatNobodyConnectedIsLeftToRemindAbout<id>__list_tools/<id>__call_toolper user plugin; first call without a login answersauthorization_requiredUserPluginsSuite, live runplugins.ErrUnauthorized; the runner drops the refused login and asks againTestALoginTheProviderRefusesIsAskedForAgain,TestALoginRevokedMidConversationIsAskedForAgain,TestALoginTheServerRefusesIsToldApartplugin_authorizationChat attachment on the reply (shown whether or not the tool is visible, only from the plugin's own tool)TestALoginAPluginAsksForIsAttachedToTheReplyAndRestored,TestOnlyThePluginsOwnToolMayAskForItsLoginlegacy.yamlto Huma, souser_pluginsandnot_connectedare declared in GolistPluginsPluginsSuite,ConfigsSuiteTestACallerWithNoCredentialIsToldToAuthenticateFirstAccelerated(user_id=),authorization_requiredevent,respondnumbered for a user's kept conversation,ask()followspending_worktest_accelerated.py,test_a_reply_that_runs_tools_is_followed_until_it_answersuser_plugins; clients regeneratedTestTheDeclarationSaysWhoConnectsEachPluginexamples/text_agents/on_callLive run
Local router in
proxymode with Postgres, Redis, real LLMs and real Stream Chat, plusuv run on_call.pyas useralice. Google Calendar used a placeholder client id.google_calendar__list_tools. The router discoveredaccounts.google.comthrough calendarmcp's path-suffixed metadata, stored a pending login foralice, and the example printed the consent URL. The URL hadscope=…calendar.readonly,access_type=offline,prompt=consent, PKCE S256 andresource.{"type":"plugin_authorization","plugin_id":"google_calendar","title":"Connect Google Calendar","authorize_url":…}.GET /v1/agents/configs/{id}/pluginsanswered[{"plugin_id":"sentry","status":"not_connected",…}].Checks:
go test ./...; theinternal/api,internal/storeandinternal/sessionintegration suites;dev.py checkrun withUV_FROZEN=1, because plainuv runfails to resolve onaccelerate(roboflow vs moondream pillow pins, pre-existing). Ruff and both mypy passes are clean, and unit tests pass (1652, 0 failed); the 44 errors are Docker-only fixtures. Known unrelated failures:TestPhoneSuitewith vendor credentials in the environment, and a flakyTestAUserRenamesASessionThatEndedthat also fails onaccelerate.Not done / unverified
status: not_connected, and a chat button forplugin_authorizationattachments.resourceparameter on consent is unverified.user_plugins(noted in thesdkskill).