From 1ea3d68e7167760f0d3b5019981db638bb73d519 Mon Sep 17 00:00:00 2001
From: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
Date: Wed, 12 Aug 2026 21:51:57 +0530
Subject: [PATCH] fix(optimize): label connector actions clearly
---
app/renderer/lib/types.ts | 2 +-
app/renderer/sections/Optimize.test.tsx | 23 +++++++++
src/dashboard.tsx | 2 +
src/optimize.ts | 14 ++++--
tests/dashboard.test.ts | 41 +++++++++++++++
tests/mcp-coverage.test.ts | 66 ++++++++++++++++++++++++-
tests/optimize.test.ts | 4 +-
7 files changed, 144 insertions(+), 8 deletions(-)
diff --git a/app/renderer/lib/types.ts b/app/renderer/lib/types.ts
index 25394087..19a652c0 100644
--- a/app/renderer/lib/types.ts
+++ b/app/renderer/lib/types.ts
@@ -403,7 +403,7 @@ export type SpendFlow = {
// ————— src/optimize.ts —————
export type WasteAction =
- | { type: 'paste'; label: string; text: string; destination?: 'claude-md' | 'session-opener' | 'prompt' | 'shell-config' }
+ | { type: 'paste'; label: string; text: string; destination?: 'claude-md' | 'session-opener' | 'prompt' | 'shell-config' | 'manual' }
| { type: 'command'; label: string; text: string }
| { type: 'file-content'; label: string; path: string; content: string }
diff --git a/app/renderer/sections/Optimize.test.tsx b/app/renderer/sections/Optimize.test.tsx
index 5219afaf..f0ef0449 100644
--- a/app/renderer/sections/Optimize.test.tsx
+++ b/app/renderer/sections/Optimize.test.tsx
@@ -160,6 +160,29 @@ describe('Optimize', () => {
expect(screen.getByText('{"batch":true}')).toBeInTheDocument()
})
+ it('renders and copies connector guidance as a manual action', async () => {
+ const report = makeOptimizeReport()
+ report.findings.push({
+ id: 'mcp-low-coverage', title: 'Underused claude.ai connector',
+ explanation: 'The connector loads unused tools.', severity: 'medium',
+ trend: null, tokensSaved: 2_000, estimatedSavingsUSD: 1,
+ fix: {
+ type: 'paste', destination: 'manual', label: 'Manage the connector where it loads:',
+ text: 'Open /mcp and disable claude.ai Google Calendar.',
+ },
+ })
+ getOptimizeReport.mockResolvedValue(report)
+ render()
+ const row = await screen.findByRole('button', { name: /Underused claude.ai connector/ })
+ fireEvent.click(row)
+
+ expect(screen.getByText('Manage the connector where it loads:')).toBeInTheDocument()
+ expect(screen.getByText('Open /mcp and disable claude.ai Google Calendar.')).toBeInTheDocument()
+ expect(row.parentElement?.querySelector('.opt-fix')).toHaveClass('opt-fix-paste')
+ fireEvent.click(screen.getByRole('button', { name: 'Copy' }))
+ await waitFor(() => expect(writeText).toHaveBeenCalledWith('Open /mcp and disable claude.ai Google Calendar.'))
+ })
+
it('switches to Reverts and Abandoned and shows only the matching yield details', async () => {
render()
await screen.findByText('Opus is doing your small talk')
diff --git a/src/dashboard.tsx b/src/dashboard.tsx
index b66b41cf..4c24579a 100644
--- a/src/dashboard.tsx
+++ b/src/dashboard.tsx
@@ -1046,6 +1046,8 @@ function actionDestinationHeader(action: WasteAction): string {
return '── Ask Claude in the current session '.padEnd(64, '─')
case 'shell-config':
return '── Add to your shell config '.padEnd(64, '─')
+ case 'manual':
+ return '── Manual action '.padEnd(64, '─')
default:
return '── Suggested action '.padEnd(64, '─')
}
diff --git a/src/optimize.ts b/src/optimize.ts
index d1b04769..b5284cb9 100644
--- a/src/optimize.ts
+++ b/src/optimize.ts
@@ -232,6 +232,7 @@ export type PasteDestination =
| 'session-opener' // one-time paste at the start of a NEW session
| 'prompt' // one-time ask in the current Claude conversation
| 'shell-config' // append to ~/.zshrc / ~/.bashrc
+ | 'manual' // instructions the user carries out directly
export type WasteAction =
| { type: 'paste'; label: string; text: string; destination?: PasteDestination }
@@ -1083,8 +1084,14 @@ export function detectMcpToolCoverage(
// so `claude mcp remove` and the file-editing apply plan cannot own them.
// Coverage is aggregate here; project-level config attribution is deliberately
// out of scope, hence the instruction to inspect /mcp per affected project.
+ const connectorLabels = connectorServers.map(server =>
+ `claude.ai ${server.slice('claude_ai_'.length).replaceAll('_', ' ')}`,
+ )
+ const connectorEvidence = connectorServers.map((server, index) =>
+ `${connectorLabels[index]} (${server})`,
+ )
const connectorGuidance = connectorServers.length > 0
- ? ` ${connectorServers.join(', ')} ${connectorServers.length === 1 ? 'is a claude.ai connector namespace' : 'are claude.ai connector namespaces'}, separate from any similarly named local MCP server. Transcript inventory is aggregated across the selected projects; use /mcp in each project where ${connectorServers.length === 1 ? 'it loads' : 'they load'}, or manage ${connectorServers.length === 1 ? 'it' : 'them'} in claude.ai Settings > Connectors.`
+ ? ` ${connectorEvidence.join(', ')} ${connectorServers.length === 1 ? 'is a claude.ai connector namespace' : 'are claude.ai connector namespaces'}, separate from any similarly named local MCP server. Transcript inventory is aggregated across the selected projects; use /mcp in each project where ${connectorServers.length === 1 ? 'it loads' : 'they load'}, or manage ${connectorServers.length === 1 ? 'it' : 'them'} in claude.ai Settings > Connectors.`
: ''
const fix: WasteAction = localServers.length > 0
? {
@@ -1096,11 +1103,11 @@ export function detectMcpToolCoverage(
}
: {
type: 'paste',
- destination: 'prompt',
+ destination: 'manual',
label: connectorServers.length === 1
? 'Manage the underused claude.ai connector where it loads:'
: 'Manage the underused claude.ai connectors where they load:',
- text: `Open /mcp in each affected project and disable ${connectorServers.join(', ')}, or manage ${connectorServers.length === 1 ? 'it' : 'them'} in claude.ai Settings > Connectors.`,
+ text: `Open /mcp in each affected project and disable ${connectorLabels.join(', ')}, or manage ${connectorServers.length === 1 ? 'it' : 'them'} in claude.ai Settings > Connectors.`,
}
return {
@@ -3152,6 +3159,7 @@ function renderActionHeader(action: WasteAction): string {
case 'session-opener': return fillTo('One-time session opener (do NOT add to CLAUDE.md)')
case 'prompt': return fillTo('Ask Claude in the current session')
case 'shell-config': return fillTo('Add to your shell config')
+ case 'manual': return fillTo('Manual action')
default: return fillTo('Suggested action')
}
}
diff --git a/tests/dashboard.test.ts b/tests/dashboard.test.ts
index 9879c8a1..d121a2cb 100644
--- a/tests/dashboard.test.ts
+++ b/tests/dashboard.test.ts
@@ -395,6 +395,47 @@ describe('interactive terminal rendering', () => {
expect(INTERACTIVE_RENDER_OPTIONS).toMatchObject({ alternateScreen: true })
})
+ it('labels claude.ai connector remediation as a manual action', async () => {
+ const stdin = new PassThrough() as PassThrough & NodeJS.ReadStream
+ const stdout = new PassThrough() as PassThrough & NodeJS.WriteStream
+ stdin.isTTY = true
+ stdin.setRawMode = () => stdin
+ stdin.ref = () => stdin
+ stdin.unref = () => stdin
+ stdout.isTTY = true
+ stdout.columns = 120
+ stdout.rows = 50
+ const frames: string[] = []
+ stdout.on('data', chunk => frames.push(stripAnsi(String(chunk))))
+
+ const inventory = Array.from({ length: 20 }, (_, i) => `mcp__claude_ai_Google_Calendar__t${i}`)
+ const sessions = ['connector-a', 'connector-b'].map((id, index) => {
+ const session = makeSession(id, 91.337 + index)
+ session.mcpInventory = inventory
+ return session
+ })
+ const app = render(React.createElement(InteractiveDashboard, {
+ initialProjects: [makeProject('connector-manual-action', sessions)],
+ initialPeriod: 'today',
+ initialProvider: 'all',
+ refreshSeconds: 0,
+ windowColumns: 120,
+ }), { stdin, stdout, debug: true, interactive: true, patchConsole: false })
+ onTestFinished(() => app.unmount())
+
+ await app.waitUntilRenderFlush()
+ stdin.write('o')
+ let frame = ''
+ for (let i = 0; i < 100 && !frame.includes('Manual action'); i++) {
+ await new Promise(resolve => setTimeout(resolve, 10))
+ frame = frames.filter(value => value.trim()).at(-1) ?? ''
+ }
+
+ expect(frame).toContain('Manual action')
+ expect(frame).toContain('claude.ai Google Calendar')
+ expect(frame).not.toContain('Ask Claude in the current session')
+ })
+
it('leaves resize frame synchronization entirely to Ink', () => {
const source = readFileSync(new URL('../src/dashboard.tsx', import.meta.url), 'utf8')
expect(source).not.toContain('process.stdout.write(BSU)')
diff --git a/tests/mcp-coverage.test.ts b/tests/mcp-coverage.test.ts
index c036301b..6a18c325 100644
--- a/tests/mcp-coverage.test.ts
+++ b/tests/mcp-coverage.test.ts
@@ -1,10 +1,12 @@
-import { describe, it, expect } from 'vitest'
+import { describe, it, expect, vi } from 'vitest'
import {
aggregateMcpCoverage,
+ buildOptimizeJsonReport,
detectMcpProfileAdvisor,
detectMcpToolCoverage,
estimateMcpSchemaCost,
+ runOptimize,
} from '../src/optimize.js'
import type {
ClassifiedTurn,
@@ -346,19 +348,78 @@ describe('detectMcpToolCoverage', () => {
expect(finding).not.toBeNull()
expect(finding!.tokensSaved).toBe(20_000)
+ // Keep the transcript namespace as evidence, but name the connector the
+ // way users actually see it in /mcp and claude.ai Settings.
expect(finding!.explanation).toContain(server)
+ expect(finding!.explanation).toContain('claude.ai Netlify')
expect(finding!.explanation).toContain('/mcp')
expect(finding!.explanation).toContain('claude.ai Settings > Connectors')
expect(finding!.fix.type).toBe('paste')
if (finding!.fix.type === 'paste') {
- expect(finding!.fix.destination).toBe('prompt')
+ expect(finding!.fix.destination).toBe('manual')
expect(finding!.fix.text).toContain('/mcp')
+ expect(finding!.fix.text).toContain('claude.ai Netlify')
+ expect(finding!.fix.text).not.toContain(server)
expect(finding!.fix.text).toContain('claude.ai Settings > Connectors')
}
expect(JSON.stringify(finding)).not.toContain('claude mcp remove')
expect(finding!.apply).toBeUndefined()
})
+ it('renders connector-only remediation as a manual action, never an Ask Claude prompt', async () => {
+ const server = 'claude_ai_Google_Calendar'
+ const inventory = Array.from({ length: 20 }, (_, i) => `mcp__${server}__t${i}`)
+ const turns = [makeTurn([makeCall({ cacheCreation: 50_000 })])]
+ const projects = [project([
+ makeSession({ sessionId: 'a', inventory, turns }),
+ makeSession({ sessionId: 'b', inventory, turns }),
+ ])]
+ const log = vi.spyOn(console, 'log').mockImplementation(() => undefined)
+
+ try {
+ await runOptimize(projects, 'Test period')
+ const output = log.mock.calls.map(args => args.join(' ')).join('\n')
+ expect(output).toContain('Manual action')
+ expect(output).toContain('claude.ai Google Calendar')
+ expect(output).not.toContain('Ask Claude in the current session')
+ } finally {
+ log.mockRestore()
+ }
+ })
+
+ it('keeps the public optimize JSON envelope while marking connector guidance manual', () => {
+ const server = 'claude_ai_Slack'
+ const coverage = [{
+ server,
+ toolsAvailable: 20,
+ toolsInvoked: 0,
+ unusedTools: Array.from({ length: 20 }, (_, i) => `mcp__${server}__t${i}`),
+ invocations: 0,
+ loadedSessions: 2,
+ coverageRatio: 0,
+ }]
+ const finding = detectMcpToolCoverage([], coverage)!
+
+ const report = buildOptimizeJsonReport([], 'Test period', {
+ findings: [finding],
+ costRate: 0,
+ healthScore: 90,
+ healthGrade: 'A',
+ })
+
+ expect(report.findings[0]).toMatchObject({
+ id: 'mcp-low-coverage',
+ tokensSaved: 0,
+ fix: {
+ type: 'paste',
+ destination: 'manual',
+ text: expect.stringContaining('claude.ai Slack'),
+ },
+ })
+ expect(report.findings[0]).not.toHaveProperty('apply')
+ expect(report.findings[0]).not.toHaveProperty('applyTokensSaved')
+ })
+
it('pluralises manual guidance when only claude.ai connectors are flagged', () => {
const coverage = ['claude_ai_Slack', 'claude_ai_Google_Calendar'].map(server => ({
server,
@@ -375,6 +436,7 @@ describe('detectMcpToolCoverage', () => {
expect(finding).not.toBeNull()
expect(finding!.fix).toMatchObject({
type: 'paste',
+ destination: 'manual',
label: 'Manage the underused claude.ai connectors where they load:',
})
if (finding!.fix.type === 'paste') {
diff --git a/tests/optimize.test.ts b/tests/optimize.test.ts
index bc72dd88..db14c94d 100644
--- a/tests/optimize.test.ts
+++ b/tests/optimize.test.ts
@@ -1197,9 +1197,9 @@ describe('paste-fix destination tagging (issue #277)', () => {
if (f.fix.type === 'paste') {
expect(
f.fix.destination,
- `finding "${f.title}" has paste fix without destination — pick one of: claude-md / session-opener / prompt / shell-config`
+ `finding "${f.title}" has paste fix without destination — pick one of: claude-md / session-opener / prompt / shell-config / manual`
).toBeDefined()
- expect(['claude-md', 'session-opener', 'prompt', 'shell-config'])
+ expect(['claude-md', 'session-opener', 'prompt', 'shell-config', 'manual'])
.toContain(f.fix.destination)
}
}