Skip to content

Commit e4d389f

Browse files
committed
feat(frontend): add template and schema props to server configuration
1 parent c319dd0 commit e4d389f

File tree

3 files changed

+288
-25
lines changed

3 files changed

+288
-25
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,20 @@ onUnmounted(() => {
404404
:packages="server.packages"
405405
:remotes="server.remotes"
406406
:show-heading="false"
407+
408+
:template-args="server.template_args"
409+
:template-env="server.template_env"
410+
:template-headers="server.template_headers"
411+
412+
:team-args-schema="server.team_args_schema"
413+
:team-env-schema="server.team_env_schema"
414+
:team-headers-schema="server.team_headers_schema"
415+
:team-url-query-params-schema="server.team_url_query_params_schema"
416+
417+
:user-args-schema="server.user_args_schema"
418+
:user-env-schema="server.user_env_schema"
419+
:user-headers-schema="server.user_headers_schema"
420+
:user-url-query-params-schema="server.user_url_query_params_schema"
407421
/>
408422
</dd>
409423
</div>

services/frontend/src/components/mcp-server/view/McpServerInfoSidebar.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,21 @@ const { t } = useI18n()
7575
:runtime="server.runtime"
7676
:packages="server.packages"
7777
:remotes="server.remotes"
78+
79+
:template-args="server.template_args"
80+
:template-env="server.template_env"
81+
:template-headers="server.template_headers"
82+
:template-url-query-params="server.template_url_query_params"
83+
84+
:team-args-schema="server.team_args_schema"
85+
:team-env-schema="server.team_env_schema"
86+
:team-headers-schema="server.team_headers_schema"
87+
:team-url-query-params-schema="server.team_url_query_params_schema"
88+
89+
:user-args-schema="server.user_args_schema"
90+
:user-env-schema="server.user_env_schema"
91+
:user-headers-schema="server.user_headers_schema"
92+
:user-url-query-params-schema="server.user_url_query_params_schema"
7893
/>
7994
</div>
8095
</div>

services/frontend/src/components/mcp-server/view/McpServerInfoSpecifications.vue

Lines changed: 259 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,270 @@ import { Shield, Package, Globe } from 'lucide-vue-next'
77
interface Props {
88
requiresOauth?: boolean | null
99
runtime: string
10+
showHeading?: boolean
11+
12+
// Keep for backward compatibility (will use to extract base URL)
13+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
14+
remotes?: any | null
1015
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1116
packages?: any | null
17+
18+
// Template Level (can be array, object, or string depending on API source)
1219
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13-
remotes?: any | null
14-
showHeading?: boolean
20+
templateArgs?: any[] | Record<string, any> | string | null
21+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22+
templateEnv?: any[] | Record<string, any> | string | null
23+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
24+
templateHeaders?: any[] | Record<string, any> | string | null
25+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26+
templateUrlQueryParams?: any[] | Record<string, any> | string | null
27+
28+
// Team Level Schemas (can be array or string depending on API source)
29+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
30+
teamArgsSchema?: any[] | string | null
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
32+
teamEnvSchema?: any[] | string | null
33+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
34+
teamHeadersSchema?: any[] | string | null
35+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
36+
teamUrlQueryParamsSchema?: any[] | string | null
37+
38+
// User Level Schemas (can be array or string depending on API source)
39+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
40+
userArgsSchema?: any[] | string | null
41+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
42+
userEnvSchema?: any[] | string | null
43+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
44+
userHeadersSchema?: any[] | string | null
45+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
46+
userUrlQueryParamsSchema?: any[] | string | null
1547
}
1648
1749
const props = withDefaults(defineProps<Props>(), {
1850
showHeading: true
1951
})
2052
21-
// Format JSON for display
22-
const formattedPackages = computed(() => {
23-
if (!props.packages) return ''
24-
return JSON.stringify(props.packages, null, 2)
53+
// Assemble final specification
54+
const assembledSpecification = computed(() => {
55+
if (props.runtime === 'http') {
56+
// HTTP Server Assembly
57+
if (!props.remotes || !Array.isArray(props.remotes) || props.remotes.length === 0) {
58+
return null
59+
}
60+
61+
const remote = props.remotes[0]
62+
63+
// Extract base URL (remove query params)
64+
let baseUrl = remote.url || ''
65+
try {
66+
const url = new URL(baseUrl)
67+
baseUrl = `${url.protocol}//${url.host}${url.pathname}`
68+
} catch {
69+
// If URL parsing fails, use as-is
70+
}
71+
72+
// Merge headers from all 3 tiers
73+
const headers: Record<string, string> = {}
74+
75+
// Template headers (actual values) - handle both array and object formats
76+
if (props.templateHeaders) {
77+
if (Array.isArray(props.templateHeaders)) {
78+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
79+
props.templateHeaders.forEach((header: any) => {
80+
if (header.name) {
81+
headers[header.name] = header.value || ''
82+
}
83+
})
84+
} else if (typeof props.templateHeaders === 'object') {
85+
Object.entries(props.templateHeaders).forEach(([key, value]) => {
86+
headers[key] = String(value || '')
87+
})
88+
}
89+
}
90+
91+
// Team headers schema (placeholder values)
92+
if (props.teamHeadersSchema && Array.isArray(props.teamHeadersSchema)) {
93+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
94+
props.teamHeadersSchema.forEach((schema: any) => {
95+
if (schema.name) {
96+
headers[schema.name] = schema.default || ''
97+
}
98+
})
99+
}
100+
101+
// User headers schema (placeholder values)
102+
if (props.userHeadersSchema && Array.isArray(props.userHeadersSchema)) {
103+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
104+
props.userHeadersSchema.forEach((schema: any) => {
105+
if (schema.name) {
106+
headers[schema.name] = schema.default || ''
107+
}
108+
})
109+
}
110+
111+
// Merge query params from all 3 tiers
112+
const queryParams: Record<string, string> = {}
113+
114+
// Template query params (actual values) - handle both array and object formats
115+
if (props.templateUrlQueryParams) {
116+
if (Array.isArray(props.templateUrlQueryParams)) {
117+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
118+
props.templateUrlQueryParams.forEach((param: any) => {
119+
if (param.name) {
120+
queryParams[param.name] = param.value || ''
121+
}
122+
})
123+
} else if (typeof props.templateUrlQueryParams === 'object') {
124+
Object.entries(props.templateUrlQueryParams).forEach(([key, value]) => {
125+
queryParams[key] = String(value || '')
126+
})
127+
}
128+
}
129+
130+
// Team query params schema (placeholder values)
131+
if (props.teamUrlQueryParamsSchema && Array.isArray(props.teamUrlQueryParamsSchema)) {
132+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
133+
props.teamUrlQueryParamsSchema.forEach((schema: any) => {
134+
if (schema.name) {
135+
queryParams[schema.name] = schema.default || 'YOUR_API_TOKEN_HERE'
136+
}
137+
})
138+
}
139+
140+
// User query params schema (placeholder values)
141+
if (props.userUrlQueryParamsSchema && Array.isArray(props.userUrlQueryParamsSchema)) {
142+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
143+
props.userUrlQueryParamsSchema.forEach((schema: any) => {
144+
if (schema.name) {
145+
queryParams[schema.name] = schema.default || ''
146+
}
147+
})
148+
}
149+
150+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
151+
const spec: any = {
152+
type: remote.type || 'streamable-http',
153+
url: baseUrl
154+
}
155+
156+
if (Object.keys(headers).length > 0) {
157+
spec.headers = headers
158+
}
159+
160+
if (Object.keys(queryParams).length > 0) {
161+
spec.query_params = queryParams
162+
}
163+
164+
return spec
165+
} else {
166+
// STDIO Server Assembly
167+
if (!props.packages || !Array.isArray(props.packages) || props.packages.length === 0) {
168+
return null
169+
}
170+
171+
const pkg = props.packages[0]
172+
173+
// Extract command (usually "npx" or "node")
174+
const command = typeof pkg === 'string' ? pkg.split(' ')[0] : 'npx'
175+
176+
// Merge args from all 3 tiers
177+
const args: string[] = []
178+
179+
// Template args (actual values) - handle both array and object formats
180+
if (props.templateArgs) {
181+
if (Array.isArray(props.templateArgs)) {
182+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
183+
props.templateArgs.forEach((arg: any) => {
184+
if (typeof arg === 'string') {
185+
args.push(arg)
186+
} else if (arg.value) {
187+
args.push(arg.value)
188+
}
189+
})
190+
} else if (typeof props.templateArgs === 'object') {
191+
Object.values(props.templateArgs).forEach((value) => {
192+
if (value) {
193+
args.push(String(value))
194+
}
195+
})
196+
}
197+
}
198+
199+
// Team args schema (placeholder values)
200+
if (props.teamArgsSchema && Array.isArray(props.teamArgsSchema)) {
201+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
202+
props.teamArgsSchema.forEach((schema: any) => {
203+
args.push(schema.default || `<${schema.name}>`)
204+
})
205+
}
206+
207+
// User args schema (placeholder values)
208+
if (props.userArgsSchema && Array.isArray(props.userArgsSchema)) {
209+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
210+
props.userArgsSchema.forEach((schema: any) => {
211+
args.push(schema.default || `<${schema.name}>`)
212+
})
213+
}
214+
215+
// Merge env from all 3 tiers
216+
const env: Record<string, string> = {}
217+
218+
// Template env (actual values) - handle both array and object formats
219+
if (props.templateEnv) {
220+
if (Array.isArray(props.templateEnv)) {
221+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
222+
props.templateEnv.forEach((envVar: any) => {
223+
if (envVar.name) {
224+
env[envVar.name] = envVar.value || ''
225+
}
226+
})
227+
} else if (typeof props.templateEnv === 'object') {
228+
Object.entries(props.templateEnv).forEach(([key, value]) => {
229+
env[key] = String(value || '')
230+
})
231+
}
232+
}
233+
234+
// Team env schema (placeholder values)
235+
if (props.teamEnvSchema && Array.isArray(props.teamEnvSchema)) {
236+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
237+
props.teamEnvSchema.forEach((schema: any) => {
238+
if (schema.name) {
239+
env[schema.name] = schema.default || ''
240+
}
241+
})
242+
}
243+
244+
// User env schema (placeholder values)
245+
if (props.userEnvSchema && Array.isArray(props.userEnvSchema)) {
246+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
247+
props.userEnvSchema.forEach((schema: any) => {
248+
if (schema.name) {
249+
env[schema.name] = schema.default || ''
250+
}
251+
})
252+
}
253+
254+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
255+
const spec: any = {
256+
command
257+
}
258+
259+
if (args.length > 0) {
260+
spec.args = args
261+
}
262+
263+
if (Object.keys(env).length > 0) {
264+
spec.env = env
265+
}
266+
267+
return spec
268+
}
25269
})
26270
27-
const formattedRemotes = computed(() => {
28-
if (!props.remotes) return ''
29-
return JSON.stringify(props.remotes, null, 2)
271+
const formattedSpecification = computed(() => {
272+
if (!assembledSpecification.value) return ''
273+
return JSON.stringify(assembledSpecification.value, null, 2)
30274
})
31275
</script>
32276

@@ -48,25 +292,15 @@ const formattedRemotes = computed(() => {
48292
</dd>
49293
</div>
50294

51-
<!-- Packages (if runtime !== 'http') -->
52-
<div v-if="runtime !== 'http' && packages" class="space-y-1">
53-
<dt class="text-xs font-medium text-muted-foreground flex items-center gap-1">
54-
<Package class="h-3 w-3" />
55-
Packages
56-
</dt>
57-
<dd class="text-sm">
58-
<CodeHighlight :code="formattedPackages" language="json" />
59-
</dd>
60-
</div>
61-
62-
<!-- Remotes (if runtime === 'http') -->
63-
<div v-if="runtime === 'http' && remotes" class="space-y-1">
295+
<!-- Assembled Specification -->
296+
<div v-if="assembledSpecification" class="space-y-1">
64297
<dt class="text-xs font-medium text-muted-foreground flex items-center gap-1">
65-
<Globe class="h-3 w-3" />
66-
Remotes
298+
<Package v-if="runtime !== 'http'" class="h-3 w-3" />
299+
<Globe v-else class="h-3 w-3" />
300+
{{ runtime === 'http' ? 'Remote Configuration' : 'Package Configuration' }}
67301
</dt>
68302
<dd class="text-sm">
69-
<CodeHighlight :code="formattedRemotes" language="json" />
303+
<CodeHighlight :code="formattedSpecification" language="json" />
70304
</dd>
71305
</div>
72306
</div>

0 commit comments

Comments
 (0)