Skip to content

Commit 43a5fab

Browse files
author
Lasim
committed
feat(frontend): add user arguments and configuration sections
1 parent c4aa090 commit 43a5fab

File tree

4 files changed

+261
-29
lines changed

4 files changed

+261
-29
lines changed

services/frontend/src/components/mcp-server/installation/TeamConfiguration.vue

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
/* eslint-disable @typescript-eslint/no-explicit-any */
3-
3+
44
import { ref, computed, onMounted } from 'vue'
55
import { useI18n } from 'vue-i18n'
66
import { toast } from 'vue-sonner'
@@ -89,6 +89,16 @@ const teamEnvSchema = computed(() => {
8989
}
9090
})
9191
92+
const userArgsSchema = computed(() => {
93+
const schema = props.installation.server?.user_args_schema || serverData.value?.user_args_schema
94+
if (!schema) return []
95+
try {
96+
return Array.isArray(schema) ? schema : JSON.parse(schema)
97+
} catch {
98+
return []
99+
}
100+
})
101+
92102
const userEnvSchema = computed(() => {
93103
const schema = props.installation.server?.user_env_schema || serverData.value?.user_env_schema
94104
if (!schema) return []
@@ -135,7 +145,7 @@ const userEnvWithData = computed(() => {
135145
136146
// Check if there's any team configuration
137147
const hasTeamConfiguration = computed(() => {
138-
return teamArgsSchema.value.length > 0 || teamEnvSchema.value.length > 0 || userEnvSchema.value.length > 0
148+
return teamArgsSchema.value.length > 0 || teamEnvSchema.value.length > 0 || userArgsSchema.value.length > 0 || userEnvSchema.value.length > 0
139149
})
140150
141151
// Modal functions
@@ -275,8 +285,8 @@ const modalTitle = computed(() => {
275285
<p class="text-xs text-gray-500">{{ t('mcpInstallations.teamConfiguration.sections.teamArgs.description') }}</p>
276286
</div>
277287

278-
<ul role="list" class="divide-y divide-gray-100">
279-
<li v-for="arg in teamArgsWithData" :key="arg.index" class="flex items-center justify-between gap-x-6 py-5">
288+
<ul role="list" class="space-y-3">
289+
<li v-for="arg in teamArgsWithData" :key="arg.index" class="flex items-center justify-between gap-x-6 py-5 bg-muted/50 rounded-lg px-4">
280290
<div class="min-w-0 flex-1">
281291
<div class="flex items-start gap-x-3">
282292
<p class="text-sm/6 font-semibold text-gray-900 font-mono">
@@ -398,7 +408,63 @@ const modalTitle = computed(() => {
398408
</div>
399409

400410
<!-- Separator -->
401-
<hr v-if="teamEnvSchema.length > 0 && userEnvSchema.length > 0" class="my-8 border-gray-200" />
411+
<hr v-if="(teamArgsSchema.length > 0 || teamEnvSchema.length > 0) && (userArgsSchema.length > 0 || userEnvSchema.length > 0)" class="my-8 border-gray-200" />
412+
413+
<!-- User Arguments Section (Read-only Info) -->
414+
<div v-if="userArgsSchema.length > 0">
415+
<div class="mb-4">
416+
<h4 class="text-sm font-semibold text-gray-900">{{ t('mcpInstallations.teamConfiguration.sections.userArgs.title') }}</h4>
417+
<p class="text-xs text-gray-500">
418+
{{ t('mcpInstallations.teamConfiguration.sections.userArgs.description') }}
419+
</p>
420+
</div>
421+
422+
<ul role="list" class="space-y-3">
423+
<li v-for="(arg, index) in userArgsSchema" :key="`user_arg_${index}`" class="flex items-center justify-between gap-x-6 py-5 bg-muted/50 rounded-lg px-4">
424+
<div class="min-w-0 flex-1">
425+
<div class="flex items-start gap-x-3">
426+
<p class="text-sm/6 font-semibold text-gray-900 font-mono">
427+
{{ arg.name }}
428+
</p>
429+
</div>
430+
<div class="mt-1 text-xs/5 text-gray-700">
431+
<span class="font-medium text-gray-800">{{ t('mcpInstallations.teamConfiguration.table.labels.required') }}</span>
432+
<span class="ml-1">{{ arg.required ? t('common.labels.yes') : t('common.labels.no') }}</span>
433+
</div>
434+
</div>
435+
436+
<div class="flex-1 min-w-0">
437+
<div class="space-y-1 text-xs/5 text-gray-700">
438+
<div>
439+
<span class="font-medium text-gray-800">{{ t('mcpInstallations.teamConfiguration.table.labels.type') }}</span>
440+
<span class="ml-1">{{ arg.type || t('mcpInstallations.teamConfiguration.table.labels.defaultType') }}</span>
441+
</div>
442+
<div v-if="arg.description">
443+
<span class="font-medium text-gray-800">{{ t('mcpInstallations.teamConfiguration.table.labels.description') }}</span>
444+
<span class="ml-1">{{ arg.description }}</span>
445+
</div>
446+
<div>
447+
<span class="font-medium text-gray-800">{{ t('mcpInstallations.teamConfiguration.table.labels.value') }}</span>
448+
<span class="ml-1 font-mono">
449+
{{ t('mcpInstallations.teamConfiguration.sections.userEnv.userConfigured') }}
450+
</span>
451+
</div>
452+
</div>
453+
</div>
454+
455+
<div class="flex flex-none items-center gap-x-4">
456+
<Button
457+
size="sm"
458+
variant="outline"
459+
disabled
460+
class="cursor-not-allowed opacity-50"
461+
>
462+
{{ t('mcpInstallations.teamConfiguration.table.actions.editValue') }}
463+
</Button>
464+
</div>
465+
</li>
466+
</ul>
467+
</div>
402468

403469
<!-- User Environment Variables Section (Read-only Info) -->
404470
<div v-if="userEnvSchema.length > 0">

services/frontend/src/components/mcp-server/wizard/EnvironmentVariablesStep.vue

Lines changed: 158 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,31 @@ interface EnvironmentVariable {
1515
visible_to_users?: boolean
1616
}
1717
18+
interface ArgumentSchema {
19+
name: string
20+
type?: string
21+
description?: string
22+
placeholder?: string
23+
required?: boolean
24+
locked?: boolean
25+
default_team_locked?: boolean
26+
}
27+
1828
interface ServerData {
1929
id: string
2030
name: string
2131
description?: string
2232
author_name?: string
2333
category_id?: string
34+
team_args_schema?: string | ArgumentSchema[]
2435
team_env_schema?: string | EnvironmentVariable[]
36+
user_args_schema?: string | ArgumentSchema[]
2537
user_env_schema?: string | EnvironmentVariable[]
2638
[key: string]: unknown
2739
}
2840
2941
const modelValue = defineModel<{
42+
team_args: string[]
3043
team_env: Record<string, string>
3144
user_env: Record<string, string>
3245
}>({ required: true })
@@ -55,17 +68,43 @@ const parseEnvSchema = (schema: string | EnvironmentVariable[] | undefined): Env
5568
}
5669
}
5770
71+
const parseArgsSchema = (schema: string | ArgumentSchema[] | undefined): ArgumentSchema[] => {
72+
if (!schema) return []
73+
74+
try {
75+
const parsed = typeof schema === 'string' ? JSON.parse(schema) : schema
76+
return Array.isArray(parsed) ? parsed : []
77+
} catch (error) {
78+
console.error('Error parsing arguments schema:', error)
79+
return []
80+
}
81+
}
82+
83+
const teamArgsSchema = computed(() => parseArgsSchema(props.serverData?.team_args_schema))
5884
const teamEnvSchema = computed(() => parseEnvSchema(props.serverData?.team_env_schema))
85+
const userArgsSchema = computed(() => parseArgsSchema(props.serverData?.user_args_schema))
5986
const userEnvSchema = computed(() => parseEnvSchema(props.serverData?.user_env_schema))
6087
88+
const hasTeamArgs = computed(() => teamArgsSchema.value.length > 0)
6189
const hasTeamEnvVars = computed(() => teamEnvSchema.value.length > 0)
90+
const hasUserArgs = computed(() => userArgsSchema.value.length > 0)
6291
const hasUserEnvVars = computed(() => userEnvSchema.value.length > 0)
63-
const hasAnyEnvVars = computed(() => hasTeamEnvVars.value || hasUserEnvVars.value)
92+
const hasUserConfiguration = computed(() => hasUserArgs.value || hasUserEnvVars.value)
93+
const hasAnyConfiguration = computed(() => hasTeamArgs.value || hasTeamEnvVars.value || hasUserConfiguration.value)
6494
65-
const validateTeamEnvVars = () => {
95+
const validateConfiguration = () => {
6696
const missingFields: string[] = []
6797
let isValid = true
6898
99+
// Validate team arguments
100+
teamArgsSchema.value.forEach((arg, index) => {
101+
if (arg.required && !modelValue.value.team_args[index]?.trim()) {
102+
missingFields.push(arg.name)
103+
isValid = false
104+
}
105+
})
106+
107+
// Validate team environment variables
69108
teamEnvSchema.value.forEach((envVar) => {
70109
if (envVar.required && !modelValue.value.team_env[envVar.name]?.trim()) {
71110
missingFields.push(envVar.name)
@@ -77,18 +116,29 @@ const validateTeamEnvVars = () => {
77116
return isValid
78117
}
79118
119+
watch(() => modelValue.value.team_args, () => {
120+
validateConfiguration()
121+
}, { deep: true })
122+
80123
watch(() => modelValue.value.team_env, () => {
81-
validateTeamEnvVars()
124+
validateConfiguration()
82125
}, { deep: true })
83126
84127
watch(() => props.serverData, (newData) => {
85128
if (newData) {
129+
const newTeamArgs: string[] = []
86130
const newTeamEnv: Record<string, string> = {}
87131
const newUserEnv: Record<string, string> = {}
88132
133+
const argsSchema = parseArgsSchema(newData.team_args_schema)
89134
const teamSchema = parseEnvSchema(newData.team_env_schema)
90135
const userSchema = parseEnvSchema(newData.user_env_schema)
91136
137+
// Initialize team arguments array
138+
argsSchema.forEach((arg, index) => {
139+
newTeamArgs[index] = modelValue.value.team_args?.[index] || ''
140+
})
141+
92142
teamSchema.forEach((env) => {
93143
newTeamEnv[env.name] = modelValue.value.team_env?.[env.name] || ''
94144
})
@@ -98,11 +148,12 @@ watch(() => props.serverData, (newData) => {
98148
})
99149
100150
modelValue.value = {
151+
team_args: newTeamArgs,
101152
team_env: newTeamEnv,
102153
user_env: newUserEnv
103154
}
104155
105-
validateTeamEnvVars()
156+
validateConfiguration()
106157
}
107158
}, { immediate: true })
108159
@@ -135,7 +186,57 @@ const isTextarea = (envVar: EnvironmentVariable) => {
135186
:show-details-button="false"
136187
/>
137188

138-
<div v-if="hasAnyEnvVars" class="space-y-8">
189+
<div v-if="hasAnyConfiguration" class="space-y-8">
190+
<!-- Team Arguments Section -->
191+
<div v-if="hasTeamArgs" class="bg-blue-50 p-4">
192+
<div class="mb-4">
193+
<h3 class="text-lg font-medium text-gray-900">
194+
{{ t('mcpInstallations.teamConfiguration.sections.teamArgs.title') }}
195+
</h3>
196+
<span class="text-sm text-gray-500">
197+
{{ teamArgsSchema.length }} {{ teamArgsSchema.length === 1 ? t('mcpInstallations.teamConfiguration.sections.teamArgs.counter.single') : t('mcpInstallations.teamConfiguration.sections.teamArgs.counter.plural') }}
198+
</span>
199+
</div>
200+
<p class="text-sm text-gray-600 mb-6">
201+
{{ t('mcpInstallations.teamConfiguration.sections.teamArgs.description') }}
202+
</p>
203+
204+
<div class="space-y-4">
205+
<div v-for="(arg, index) in teamArgsSchema" :key="`arg_${index}`" class="space-y-2">
206+
<div class="flex items-center gap-2">
207+
<Label :for="`team_arg_${index}`" class="flex items-center gap-2">
208+
{{ arg.name }}
209+
<span v-if="arg.required" class="text-xs text-gray-500">
210+
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.required') }}
211+
</span>
212+
<span v-else class="text-xs text-gray-500">
213+
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.optional') }}
214+
</span>
215+
</Label>
216+
</div>
217+
218+
<div v-if="arg.description" class="text-sm text-gray-600">
219+
{{ arg.description }}
220+
</div>
221+
222+
<div class="relative">
223+
<Input
224+
:id="`team_arg_${index}`"
225+
type="text"
226+
v-model="modelValue.team_args[index]"
227+
:placeholder="arg.placeholder || t('mcpInstallations.teamConfiguration.editModal.form.placeholders.enterValue')"
228+
:required="arg.required"
229+
/>
230+
</div>
231+
232+
<div v-if="arg.type" class="text-xs text-gray-500">
233+
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.typeLabel') }} <code class="bg-gray-100 px-1 rounded">{{ arg.type }}</code>
234+
</div>
235+
</div>
236+
</div>
237+
</div>
238+
239+
<!-- Team Environment Variables Section -->
139240
<div v-if="hasTeamEnvVars" class="bg-gray-50 p-4">
140241
<div class="mb-4">
141242
<h3 class="text-lg font-medium text-gray-900">
@@ -197,13 +298,14 @@ const isTextarea = (envVar: EnvironmentVariable) => {
197298
</div>
198299
</div>
199300

200-
<div v-if="hasUserEnvVars" class="bg-gray-50 p-4">
301+
<!-- User Configuration Section -->
302+
<div v-if="hasUserConfiguration" class="bg-gray-50 p-4">
201303
<div class="mb-4">
202304
<h3 class="text-lg font-medium text-gray-900">
203-
{{ t('mcpInstallations.teamConfiguration.sections.userEnv.title') }}
305+
{{ t('mcpInstallations.teamConfiguration.sections.userConfig.title') }}
204306
</h3>
205307
<span class="text-sm text-gray-500">
206-
{{ userEnvSchema.length }} {{ userEnvSchema.length === 1 ? t('mcpInstallations.teamConfiguration.sections.teamEnv.counter.single') : t('mcpInstallations.teamConfiguration.sections.teamEnv.counter.plural') }}
308+
{{ (userArgsSchema.length + userEnvSchema.length) }} {{ (userArgsSchema.length + userEnvSchema.length) === 1 ? t('mcpInstallations.teamConfiguration.sections.userConfig.counter.single') : t('mcpInstallations.teamConfiguration.sections.userConfig.counter.plural') }}
207309
</span>
208310
</div>
209311

@@ -215,24 +317,57 @@ const isTextarea = (envVar: EnvironmentVariable) => {
215317
</div>
216318

217319
<div class="space-y-4">
218-
<div v-for="envVar in userEnvSchema" :key="envVar.name" class="bg-white p-4">
219-
<div class="flex items-center gap-2 mb-2">
220-
<span class="font-medium text-gray-900 font-mono">{{ envVar.name }}</span>
221-
<span v-if="envVar.required" class="text-xs text-gray-500">
222-
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.required') }}
223-
</span>
224-
<span v-else class="text-xs text-gray-500">
225-
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.optional') }}
226-
</span>
227-
</div>
320+
<!-- User Arguments -->
321+
<div v-if="hasUserArgs" class="space-y-3">
322+
<h4 class="text-sm font-semibold text-gray-900 border-b border-gray-200 pb-2">
323+
{{ t('mcpInstallations.teamConfiguration.sections.userArgs.title') }}
324+
</h4>
325+
<div v-for="(arg, index) in userArgsSchema" :key="`user_arg_${index}`" class="bg-white p-4 rounded-lg border">
326+
<div class="flex items-center gap-2 mb-2">
327+
<span class="font-medium text-gray-900 font-mono">{{ arg.name }}</span>
328+
<span v-if="arg.required" class="text-xs text-gray-500">
329+
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.required') }}
330+
</span>
331+
<span v-else class="text-xs text-gray-500">
332+
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.optional') }}
333+
</span>
334+
</div>
228335

229-
<div v-if="envVar.description" class="text-sm text-gray-600 mb-2">
230-
{{ envVar.description }}
336+
<div v-if="arg.description" class="text-sm text-gray-600 mb-2">
337+
{{ arg.description }}
338+
</div>
339+
340+
<div class="flex items-center gap-4 text-xs text-gray-500">
341+
<span>{{ t('mcpInstallations.teamConfiguration.userEnvDetails.typeLabel') }} <code class="bg-gray-100 px-1 rounded">{{ arg.type || 'string' }}</code></span>
342+
<span v-if="arg.placeholder">{{ t('mcpInstallations.teamConfiguration.userEnvDetails.placeholderLabel') }} "{{ arg.placeholder }}"</span>
343+
</div>
231344
</div>
345+
</div>
346+
347+
<!-- User Environment Variables -->
348+
<div v-if="hasUserEnvVars" class="space-y-3">
349+
<h4 class="text-sm font-semibold text-gray-900 border-b border-gray-200 pb-2">
350+
{{ t('mcpInstallations.teamConfiguration.sections.userEnv.title') }}
351+
</h4>
352+
<div v-for="envVar in userEnvSchema" :key="envVar.name" class="bg-white p-4 rounded-lg border">
353+
<div class="flex items-center gap-2 mb-2">
354+
<span class="font-medium text-gray-900 font-mono">{{ envVar.name }}</span>
355+
<span v-if="envVar.required" class="text-xs text-gray-500">
356+
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.required') }}
357+
</span>
358+
<span v-else class="text-xs text-gray-500">
359+
{{ t('mcpInstallations.teamConfiguration.userEnvDetails.optional') }}
360+
</span>
361+
</div>
362+
363+
<div v-if="envVar.description" class="text-sm text-gray-600 mb-2">
364+
{{ envVar.description }}
365+
</div>
232366

233-
<div class="flex items-center gap-4 text-xs text-gray-500">
234-
<span>{{ t('mcpInstallations.teamConfiguration.userEnvDetails.typeLabel') }} <code class="bg-gray-100 px-1 rounded">{{ envVar.type || 'string' }}</code></span>
235-
<span v-if="envVar.placeholder">{{ t('mcpInstallations.teamConfiguration.userEnvDetails.placeholderLabel') }} "{{ envVar.placeholder }}"</span>
367+
<div class="flex items-center gap-4 text-xs text-gray-500">
368+
<span>{{ t('mcpInstallations.teamConfiguration.userEnvDetails.typeLabel') }} <code class="bg-gray-100 px-1 rounded">{{ envVar.type || 'string' }}</code></span>
369+
<span v-if="envVar.placeholder">{{ t('mcpInstallations.teamConfiguration.userEnvDetails.placeholderLabel') }} "{{ envVar.placeholder }}"</span>
370+
</div>
236371
</div>
237372
</div>
238373
</div>

0 commit comments

Comments
 (0)