mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
feat(mcp): always register list_trips & get_trip_summary; inject deprecation notice into tool results
Navigation tools: - list_trips and get_trip_summary are now always registered for any OAuth session regardless of granted scopes — they are required for trip ID discovery before any scoped tool can be used - get_trip_summary filters optional sections (budget, packing, collab, reservations) by the client's OAuth scopes when called without trips:read Deprecation notice: - Inject static token deprecation warning into the first tool result (list_trips or get_trip_summary) via a per-session closure so Claude is forced to surface it — the instructions field alone is only background context and is not proactively shown to the user UI: - OAuth client creation modal: add hint explaining the always-available tools, remove the "must select at least one scope" submit guard - OAuth consent screen: add "Always included" section showing list_trips and get_trip_summary; handles zero-scope clients gracefully (empty permissions section is hidden)
This commit is contained in:
@@ -552,7 +552,7 @@ export default function IntegrationsTab(): React.ReactElement {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>{t('settings.oauth.modal.scopes')}</label>
|
||||
<button type="button"
|
||||
onClick={() => {
|
||||
@@ -567,6 +567,7 @@ export default function IntegrationsTab(): React.ReactElement {
|
||||
: t('settings.oauth.modal.selectAll')}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs mb-2" style={{ color: 'var(--text-tertiary)' }}>{t('settings.oauth.modal.scopesHint')}</p>
|
||||
<div className="space-y-1 max-h-56 overflow-y-auto pr-1">
|
||||
{Object.entries(scopesByGroup).map(([group, groupScopes]) => {
|
||||
const groupScopeKeys = groupScopes.map(s => s.scope)
|
||||
@@ -625,7 +626,7 @@ export default function IntegrationsTab(): React.ReactElement {
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button onClick={handleCreateOAuthClient}
|
||||
disabled={!oauthNewName.trim() || !oauthNewUris.trim() || oauthNewScopes.length === 0 || oauthCreating}
|
||||
disabled={!oauthNewName.trim() || !oauthNewUris.trim() || oauthCreating}
|
||||
className="px-4 py-2 rounded-lg text-sm font-medium text-white disabled:opacity-50"
|
||||
style={{ background: 'var(--accent-primary, #4f46e5)' }}>
|
||||
{oauthCreating ? t('settings.oauth.modal.creating') : t('settings.oauth.modal.create')}
|
||||
|
||||
@@ -299,6 +299,7 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth',
|
||||
'settings.oauth.modal.redirectUrisHint': 'One URI per line. HTTPS required (localhost exempt). Exact match enforced.',
|
||||
'settings.oauth.modal.scopes': 'Allowed Scopes',
|
||||
'settings.oauth.modal.scopesHint': 'list_trips and get_trip_summary are always available — no scope required. They let the AI discover trip IDs needed to use any other tool.',
|
||||
'settings.oauth.modal.selectAll': 'Select all',
|
||||
'settings.oauth.modal.deselectAll': 'Deselect all',
|
||||
'settings.oauth.modal.creating': 'Registering…',
|
||||
|
||||
@@ -215,31 +215,58 @@ export default function OAuthAuthorizePage(): React.ReactElement {
|
||||
|
||||
{/* Right panel — scopes */}
|
||||
<div className="flex-1 px-6 py-8 overflow-y-auto max-h-[80vh] sm:max-h-[600px]">
|
||||
<p className="text-xs font-medium uppercase tracking-wide mb-4" style={{ color: 'var(--text-tertiary)' }}>
|
||||
Permissions requested
|
||||
</p>
|
||||
<div className="space-y-5">
|
||||
{Object.entries(scopesByGroup).map(([group, groupScopes]) => (
|
||||
<div key={group}>
|
||||
<p className="text-xs font-semibold mb-2" style={{ color: 'var(--text-secondary)' }}>{group}</p>
|
||||
<div className="space-y-1.5">
|
||||
{groupScopes.map(s => {
|
||||
const info = SCOPE_GROUPS[s]
|
||||
return (
|
||||
<div key={s} className="flex items-start gap-2.5 px-3 py-2 rounded-lg" style={{ background: 'var(--bg-secondary)' }}>
|
||||
<span className="mt-0.5 text-base leading-none flex-shrink-0">
|
||||
{s.endsWith(':delete') ? '🗑️' : s.endsWith(':write') ? '✏️' : '👁️'}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>{info?.label || s}</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: 'var(--text-tertiary)' }}>{info?.description || ''}</p>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{Object.keys(scopesByGroup).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs font-medium uppercase tracking-wide mb-4" style={{ color: 'var(--text-tertiary)' }}>
|
||||
Permissions requested
|
||||
</p>
|
||||
<div className="space-y-5">
|
||||
{Object.entries(scopesByGroup).map(([group, groupScopes]) => (
|
||||
<div key={group}>
|
||||
<p className="text-xs font-semibold mb-2" style={{ color: 'var(--text-secondary)' }}>{group}</p>
|
||||
<div className="space-y-1.5">
|
||||
{groupScopes.map(s => {
|
||||
const info = SCOPE_GROUPS[s]
|
||||
return (
|
||||
<div key={s} className="flex items-start gap-2.5 px-3 py-2 rounded-lg" style={{ background: 'var(--bg-secondary)' }}>
|
||||
<span className="mt-0.5 text-base leading-none flex-shrink-0">
|
||||
{s.endsWith(':delete') ? '🗑️' : s.endsWith(':write') ? '✏️' : '👁️'}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>{info?.label || s}</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: 'var(--text-tertiary)' }}>{info?.description || ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
|
||||
{/* Always-available tools — granted regardless of scopes */}
|
||||
<div>
|
||||
<p className="text-xs font-medium uppercase tracking-wide mb-3" style={{ color: 'var(--text-tertiary)' }}>
|
||||
Always included
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
{[
|
||||
{ name: 'list_trips', desc: 'List your trips so the AI can discover trip IDs' },
|
||||
{ name: 'get_trip_summary', desc: 'Read a trip overview needed to use any other tool' },
|
||||
].map(({ name, desc }) => (
|
||||
<div key={name} className="flex items-start gap-2.5 px-3 py-2 rounded-lg" style={{ background: 'var(--bg-secondary)' }}>
|
||||
<span className="mt-0.5 text-base leading-none flex-shrink-0">👁️</span>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium font-mono" style={{ color: 'var(--text-primary)' }}>{name}</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: 'var(--text-tertiary)' }}>{desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user