McpAddPage.vue
1 <script setup lang="ts"> 2 import { ref, watch } from 'vue' 3 import { useStdioStore, CustomStdioServerParameters, StdioServerKey } from '@/renderer/store/stdio' 4 5 import ConfigJsonCard from '@/renderer/components/common/ConfigJsonCard.vue' 6 import { pickBy } from 'lodash' 7 import { useSnackbarStore } from '@/renderer/store/snackbar' 8 9 type McpServerConfig = { 10 mcpServers: McpServerObject 11 } 12 13 type McpServerObject = Record<string, CustomStdioServerParameters> 14 15 const snackbarStore = useSnackbarStore() 16 17 const stdioStore = useStdioStore() 18 19 const props = defineProps({ 20 modelValue: { 21 type: Boolean, 22 required: true 23 } 24 }) 25 26 const emit = defineEmits(['update:modelValue']) 27 28 const internalDialog = ref(props.modelValue) 29 30 watch( 31 () => props.modelValue, 32 (newVal) => { 33 internalDialog.value = newVal 34 } 35 ) 36 37 watch(internalDialog, (newVal) => { 38 emit('update:modelValue', newVal) 39 }) 40 41 const closeDialog = () => { 42 internalDialog.value = false 43 } 44 45 function findConfig(jsonConfig: McpServerConfig | McpServerObject): CustomStdioServerParameters { 46 if (!jsonConfig) return {} 47 48 const mcpConfig = jsonConfig.mcpServers ? jsonConfig.mcpServers : jsonConfig 49 50 const filtered = pickBy( 51 mcpConfig, 52 (value) => value && typeof value === 'object' && 'command' in value 53 ) 54 55 return filtered 56 } 57 58 const addConfig = () => { 59 const jsonConfigs = findConfig(jsonParams.value) 60 61 if (Object.keys(jsonConfigs).length === 0) { 62 snackbarStore.showErrorMessage('snackbar.no-mcp-config') 63 return 64 } 65 66 for (const [serverName, serverConfig] of Object.entries(jsonConfigs)) { 67 const config = serverConfig as CustomStdioServerParameters 68 ;(['command', 'args', 'env'] as StdioServerKey[]).forEach((key) => { 69 const value = config[key] 70 if (value) { 71 stdioStore.updateConfigAttribute(serverName, key, value) 72 } 73 }) 74 } 75 76 closeDialog() 77 } 78 79 const jsonError = ref<string | null>(null) 80 81 function handleError(errorMessage: string | null) { 82 jsonError.value = errorMessage 83 } 84 85 const jsonParams = ref({}) 86 87 const exampleData = [ 88 { 89 'sequential-thinking': { 90 command: 'npx', 91 args: ['-y', '@modelcontextprotocol/server-sequential-thinking'] 92 } 93 }, 94 { 95 'Time Server': { 96 command: 'uvx', 97 args: ['mcp-server-time'] 98 }, 99 memory: { 100 command: 'npx', 101 args: ['-y', '@modelcontextprotocol/server-memory'] 102 } 103 }, 104 { 105 mcpServers: { 106 filesystem: { 107 command: 'npx', 108 args: [ 109 '-y', 110 '@modelcontextprotocol/server-filesystem', 111 '/Users/username/Desktop', 112 '/path/to/other/allowed/dir' 113 ] 114 } 115 } 116 } 117 ] as const 118 </script> 119 120 <template> 121 <v-dialog v-model="internalDialog" persistent max-width="80vw" max-height="80vh" scrollable> 122 <v-card :title="$t('mcp.config')"> 123 <v-divider></v-divider> 124 <v-card-text> 125 <ConfigJsonCard v-model="jsonParams" @on-error="handleError"></ConfigJsonCard> 126 <div class="mx-4"> 127 <v-expansion-panels static color="grey-200"> 128 <v-expansion-panel 129 v-for="(value, index) in exampleData" 130 :key="index" 131 :title="`${$t('general.example')} ${index + 1}`" 132 > 133 <v-expansion-panel-text> 134 <v-textarea 135 :model-value="JSON.stringify(value, null, 2)" 136 variant="plain" 137 auto-grow 138 hide-details 139 ></v-textarea> 140 </v-expansion-panel-text> 141 </v-expansion-panel> 142 </v-expansion-panels> 143 </div> 144 </v-card-text> 145 146 <v-divider></v-divider> 147 <v-card-actions> 148 <v-spacer></v-spacer> 149 <v-btn 150 variant="plain" 151 rounded="lg" 152 icon="mdi-close-box" 153 color="error" 154 @click="closeDialog" 155 ></v-btn> 156 <v-btn 157 variant="plain" 158 rounded="lg" 159 :disabled="Boolean(jsonError ?? '')" 160 icon="mdi-content-save-plus" 161 color="success" 162 @click="addConfig()" 163 ></v-btn> 164 </v-card-actions> 165 </v-card> 166 </v-dialog> 167 </template> 168 <style scoped> 169 .v-expansion-panel-parent { 170 overflow-y: auto; 171 } 172 </style>