3333 </svg >
3434 服务器地址
3535 </label >
36- <input
37- class =" form-input"
38- v-model =" serverUrl"
39- placeholder =" http://localhost:8000/mcp"
40- :disabled =" connectionStatus === 'connected'"
41- />
36+ <div class =" url-input-wrapper" >
37+ <input
38+ class =" form-input"
39+ v-model =" serverUrl"
40+ placeholder =" http://localhost:8000/mcp"
41+ :disabled =" connectionStatus === 'connected'"
42+ />
43+ <!-- History Button -->
44+ <div class =" history-dropdown-container" >
45+ <button
46+ class =" btn-icon history-btn"
47+ @click =" showHistoryDropdown = !showHistoryDropdown"
48+ :title =" showHistoryDropdown ? '关闭历史记录' : '历史记录'"
49+ :disabled =" connectionStatus === 'connected'"
50+ >
51+ <svg width =" 16" height =" 16" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" >
52+ <circle cx =" 12" cy =" 12" r =" 10" />
53+ <polyline points =" 12 6 12 12 16 14" />
54+ </svg >
55+ </button >
56+ <!-- Dropdown Menu -->
57+ <div v-if =" showHistoryDropdown" class =" history-dropdown animate-fadeIn" >
58+ <div class =" history-title" >最近连接</div >
59+ <div v-if =" connectionHistory.length === 0" class =" history-empty" >暂无历史记录</div >
60+ <div v-else class =" history-list" >
61+ <div
62+ v-for =" (item, index) in connectionHistory"
63+ :key =" item.timestamp"
64+ class =" history-item"
65+ @click =" selectHistory(item)"
66+ >
67+ <div class =" history-item-content" >
68+ <div class =" history-url" >{{ item.url }}</div >
69+ <div class =" history-meta" >
70+ <span v-if =" item.headers.length" class =" history-badge" >{{ item.headers.length }} Headers</span >
71+ <span class =" history-time" >{{ formatHistoryTime(item.timestamp) }}</span >
72+ </div >
73+ </div >
74+ <button
75+ class =" delete-history-btn"
76+ @click.stop =" deleteHistoryItem(index)"
77+ title =" 删除此记录"
78+ >
79+ <svg width =" 14" height =" 14" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" >
80+ <line x1 =" 18" y1 =" 6" x2 =" 6" y2 =" 18" />
81+ <line x1 =" 6" y1 =" 6" x2 =" 18" y2 =" 18" />
82+ </svg >
83+ </button >
84+ </div >
85+ </div >
86+ </div >
87+ <!-- Overlay for closing -->
88+ <div
89+ v-if =" showHistoryDropdown"
90+ class =" dropdown-overlay"
91+ @click =" showHistoryDropdown = false"
92+ ></div >
93+ </div >
94+ </div >
4295 </div >
4396
4497 <!-- Headers -->
266319</template >
267320
268321<script setup lang="ts">
269- import { ref , computed } from ' vue' ;
322+ import { ref , computed , onMounted } from ' vue' ;
270323import HeaderBar from ' ./components/HeaderBar.vue' ;
271324import ToolList from ' ./components/ToolList.vue' ;
272325import ResourcePanel from ' ./components/ResourcePanel.vue' ;
273326import PromptPanel from ' ./components/PromptPanel.vue' ;
274327import type { Tool , Resource , Prompt , HeaderPair , ConnectResult , ToolCallPayload } from ' @/types' ;
275328import { mcpApi , isRunningInTauri } from ' @/services/mcpApi' ;
276329
330+ interface ConnectionConfig {
331+ url: string ;
332+ headers: HeaderPair [];
333+ timestamp: number ;
334+ }
335+
277336
278337// Theme initialization removed as we are now single-theme
279338const serverUrl = ref (' http://localhost:8000/mcp' );
@@ -290,6 +349,82 @@ const HISTORY_SUMMARY_LENGTH = 100;
290349const historyExpanded = ref (false );
291350const runningInTauri = ref (isRunningInTauri ());
292351
352+ // Connection History
353+ const connectionHistory = ref <ConnectionConfig []>([]);
354+ const showHistoryDropdown = ref (false );
355+
356+ onMounted (() => {
357+ loadHistory ();
358+ });
359+
360+ function loadHistory() {
361+ const saved = localStorage .getItem (' mcp-connection-history' );
362+ if (saved ) {
363+ try {
364+ connectionHistory .value = JSON .parse (saved );
365+ // Auto-fill from latest history if available
366+ if (connectionHistory .value .length > 0 ) {
367+ const latest = connectionHistory .value [0 ];
368+ serverUrl .value = latest .url ;
369+ if (latest .headers && latest .headers .length > 0 ) {
370+ headerInputs .value = JSON .parse (JSON .stringify (latest .headers ));
371+ }
372+ } else {
373+ // Default fallback if history array is empty
374+ serverUrl .value = ' http://localhost:8080/mcp' ;
375+ }
376+ } catch {
377+ connectionHistory .value = [];
378+ serverUrl .value = ' http://localhost:8080/mcp' ;
379+ }
380+ } else {
381+ // No history saved at all
382+ serverUrl .value = ' http://localhost:8080/mcp' ;
383+ }
384+ }
385+
386+ function saveHistory(url : string , headers : HeaderPair []) {
387+ const newConfig: ConnectionConfig = {
388+ url ,
389+ headers ,
390+ timestamp: Date .now ()
391+ };
392+
393+ // Remove duplicate (same URL) to update its position/info
394+ const existingIndex = connectionHistory .value .findIndex (c => c .url === url );
395+ if (existingIndex !== - 1 ) {
396+ connectionHistory .value .splice (existingIndex , 1 );
397+ }
398+
399+ // Add to top
400+ connectionHistory .value .unshift (newConfig );
401+
402+ // Keep top 10
403+ if (connectionHistory .value .length > 10 ) {
404+ connectionHistory .value .pop ();
405+ }
406+
407+ localStorage .setItem (' mcp-connection-history' , JSON .stringify (connectionHistory .value ));
408+ }
409+
410+ function selectHistory(config : ConnectionConfig ) {
411+ serverUrl .value = config .url ;
412+ // Deep copy headers to avoid reference issues
413+ headerInputs .value = config .headers .length > 0
414+ ? JSON .parse (JSON .stringify (config .headers ))
415+ : [{ name: ' ' , value: ' ' }];
416+ showHistoryDropdown .value = false ;
417+ }
418+
419+ function deleteHistoryItem(index : number ) {
420+ connectionHistory .value .splice (index , 1 );
421+ localStorage .setItem (' mcp-connection-history' , JSON .stringify (connectionHistory .value ));
422+ }
423+
424+ function formatHistoryTime(timestamp : number ) {
425+ return new Date (timestamp ).toLocaleString ();
426+ }
427+
293428type InspectorTab = ' tools' | ' resources' | ' prompts' ;
294429
295430interface ToolResultEntry {
@@ -434,6 +569,11 @@ async function connectToServer() {
434569 } finally {
435570 isConnecting .value = false ;
436571 }
572+
573+ // Save history on success
574+ if (connectionStatus .value === ' connected' ) {
575+ saveHistory (serverUrl .value , preparedHeaders );
576+ }
437577}
438578
439579async function disconnect() {
@@ -628,6 +768,143 @@ async function handleCallTool(payload: ToolCallPayload) {
628768 transition : all var (--transition-fast );
629769}
630770
771+ .url-input-wrapper {
772+ position : relative ;
773+ display : flex ;
774+ gap : 8px ;
775+ align-items : center ;
776+ }
777+
778+ .history-btn {
779+ flex-shrink : 0 ;
780+ width : 42px ;
781+ height : 42px ;
782+ border-radius : var (--radius-md );
783+ border-color : var (--border-light );
784+ }
785+
786+ .history-dropdown-container {
787+ position : relative ;
788+ }
789+
790+ .history-dropdown {
791+ position : absolute ;
792+ top : 100% ;
793+ right : 0 ;
794+ margin-top : 8px ;
795+ width : 320px ;
796+ background : var (--bg-card );
797+ border : 1px solid var (--border-subtle );
798+ border-radius : var (--radius-lg );
799+ box-shadow : var (--shadow-lg );
800+ z-index : 100 ;
801+ padding : 8px 0 ;
802+ backdrop-filter : blur (20px );
803+ }
804+
805+ .history-title {
806+ padding : 8px 16px ;
807+ font-size : 0.8rem ;
808+ font-weight : 600 ;
809+ color : var (--text-muted );
810+ border-bottom : 1px solid var (--border-subtle );
811+ margin-bottom : 4px ;
812+ }
813+
814+ .history-empty {
815+ padding : 24px ;
816+ text-align : center ;
817+ color : var (--text-dim );
818+ font-size : 0.85rem ;
819+ }
820+
821+ .history-list {
822+ max-height : 240px ;
823+ overflow-y : auto ;
824+ }
825+
826+ .history-item {
827+ padding : 10px 16px ;
828+ display : flex ;
829+ align-items : center ;
830+ justify-content : space-between ;
831+ cursor : pointer ;
832+ transition : background var (--transition-fast );
833+ gap : 12px ;
834+ }
835+
836+ .history-item :hover {
837+ background : var (--bg-tertiary );
838+ }
839+
840+ .history-item-content {
841+ flex : 1 ;
842+ min-width : 0 ;
843+ }
844+
845+ .history-url {
846+ font-size : 0.88rem ;
847+ color : var (--text-primary );
848+ white-space : nowrap ;
849+ overflow : hidden ;
850+ text-overflow : ellipsis ;
851+ margin-bottom : 4px ;
852+ font-family : var (--font-mono );
853+ }
854+
855+ .history-meta {
856+ display : flex ;
857+ align-items : center ;
858+ gap : 8px ;
859+ }
860+
861+ .history-badge {
862+ font-size : 0.7rem ;
863+ padding : 1px 6px ;
864+ background : var (--badge-bg );
865+ border-radius : var (--radius-full );
866+ color : var (--text-secondary );
867+ }
868+
869+ .history-time {
870+ font-size : 0.72rem ;
871+ color : var (--text-dim );
872+ }
873+
874+ .delete-history-btn {
875+ width : 24px ;
876+ height : 24px ;
877+ border : none ;
878+ background : transparent ;
879+ color : var (--text-dim );
880+ border-radius : var (--radius-full );
881+ display : flex ;
882+ align-items : center ;
883+ justify-content : center ;
884+ cursor : pointer ;
885+ opacity : 0 ;
886+ transition : all var (--transition-fast );
887+ }
888+
889+ .history-item :hover .delete-history-btn {
890+ opacity : 1 ;
891+ }
892+
893+ .delete-history-btn :hover {
894+ background : var (--error-bg );
895+ color : var (--error );
896+ }
897+
898+ .dropdown-overlay {
899+ position : fixed ;
900+ top : 0 ;
901+ left : 0 ;
902+ right : 0 ;
903+ bottom : 0 ;
904+ z-index : 90 ;
905+ cursor : default ;
906+ }
907+
631908.form-input :focus {
632909 outline : none ;
633910 border-color : var (--border-focus );
0 commit comments