viviooio/vivioo-mcp
criticalMCP Server for Vivioo agent trust infrastructure — browse agents, verify, jobs, apply.
This MCP server provides a trust infrastructure for AI agents, allowing them to browse a directory of agents, submit and verify their own profiles, ap...
418async function handleRegisterWebhook(args: Record<string, unknown>) {
419 const res = await fetchWithTimeout(`${VIVIOO_BASE}/api/notifications`, {
420 method: 'PUT',
421 headers: { 'Content-Type': 'application/json' },
422 body: JSON.stringify({
423 agentSlug: args.agentSlug,
424 editKey: args.editKey,
425 action: 'register-webhook',
426 webhookUrl: args.webhookUrl,
427 }),
428 });
429 const data = await res.json();
430 return {
431 content: [{
432 type: 'text' as const,
433 text: JSON.stringify(data, null, 2),
434 }],
435 };
436}418async function handleRegisterWebhook(args: Record<string, unknown>) {
419 const res = await fetchWithTimeout(`${VIVIOO_BASE}/api/notifications`, {
420 method: 'PUT',
421 headers: { 'Content-Type': 'application/json' },
422 body: JSON.stringify({
423 agentSlug: args.agentSlug,
424 editKey: args.editKey,
425 action: 'register-webhook',
426 webhookUrl: args.webhookUrl,
427 }),
428 });// Network-exposed MCP server; any client can call register_webhook with an arbitrary URL.
The register_webhook tool accepts a webhookUrl from the user and sends it to the Vivioo backend API. The backend likely uses this URL to send POST requests. If the backend does not validate the URL, an attacker could provide a URL pointing to internal services (e.g., http://169.254.169.254/latest/meta-data/ for cloud metadata, or http://localhost:8080/admin) to perform SSRF attacks. The MCP server itself does not validate the URL before forwarding it.
ImpactAn attacker could exploit SSRF to access internal cloud metadata, internal APIs, or other services behind the firewall, potentially leading to credential theft or further compromise.
FixValidate the webhookUrl on the MCP server side before forwarding: ensure it uses HTTPS, is not a private IP, and matches an allowlist of domains. Alternatively, rely on the backend to validate, but document that validation is required.
355async function handleVerifyGithub(args: Record<string, unknown>) {
356 const res = await fetchWithTimeout(`${VIVIOO_BASE}/api/showcase/verify/github`, {
357 method: 'POST',
358 headers: { 'Content-Type': 'application/json' },
359 body: JSON.stringify(args),
360 });// Network-exposed MCP server; any client can call verify_github with an arbitrary repoUrl.
The verify_github tool accepts a repoUrl from the user and forwards it to the Vivioo backend. The backend likely fetches the repo URL to verify it. If the backend does not validate the URL, an attacker could provide a URL pointing to internal services, enabling SSRF. The MCP server does not validate the URL before forwarding.
ImpactAn attacker could exploit SSRF to access internal cloud metadata, internal APIs, or other services behind the firewall, potentially leading to credential theft or further compromise.
FixValidate the repoUrl on the MCP server side: ensure it matches the pattern https://github.com/owner/repo and is not a private IP. Alternatively, rely on backend validation but document the requirement.
304async function handleVerifyAgent(args: Record<string, unknown>) {
305 const { slug, editKey, xHandle, tweetUrl } = args as {
306 slug: string; editKey: string; xHandle?: string; tweetUrl?: string;
307 };
308
309 if (tweetUrl) {
310 // Step 3: Submit proof
311 const res = await fetchWithTimeout(`${VIVIOO_BASE}/api/showcase/verify/submit`, {
312 method: 'POST',
313 headers: { 'Content-Type': 'application/json' },
314 body: JSON.stringify({ slug, editKey, tweetUrl }),
315 });// Network-exposed MCP server; any client can call verify_agent with an arbitrary tweetUrl.
The verify_agent tool accepts a tweetUrl from the user and forwards it to the Vivioo backend. The backend likely fetches the tweet URL to verify the post. If the backend does not validate the URL, an attacker could provide a URL pointing to internal services, enabling SSRF. The MCP server does not validate the URL before forwarding.
ImpactAn attacker could exploit SSRF to access internal cloud metadata, internal APIs, or other services behind the firewall, potentially leading to credential theft or further compromise.
FixValidate the tweetUrl on the MCP server side: ensure it matches the pattern https://twitter.com/... or https://x.com/... and is not a private IP. Alternatively, rely on backend validation but document the requirement.
355async function handleVerifyGithub(args: Record<string, unknown>) {
356 const res = await fetchWithTimeout(`${VIVIOO_BASE}/api/showcase/verify/github`, {
357 method: 'POST',
358 headers: { 'Content-Type': 'application/json' },
359 body: JSON.stringify(args),
360 });
361 const data = await res.json();
362 return {
363 content: [{
364 type: 'text' as const,
365 text: JSON.stringify(data, null, 2),
366 }],
367 };
368}// Exploitable if the backend does not validate the URL. The MCP server is exposed to untrusted prompts.
The verify_github tool accepts a repoUrl parameter and forwards it to the backend without validation. If the backend uses this URL to make requests (e.g., to fetch repo info), an attacker could provide a URL pointing to internal services, leading to SSRF. Additionally, the URL could be used for injection if not sanitized.
ImpactAn attacker could exploit SSRF to access internal resources or perform injection attacks on the backend.
FixValidate repoUrl on the MCP server: ensure it matches the pattern https://github.com/owner/repo and is a valid GitHub URL. Also sanitize the input.
242async function handleBrowseAgents(args: Record<string, unknown>) {
243 const slug = args.slug as string | undefined;
244 const url = slug
245 ? `${VIVIOO_BASE}/api/showcase?slug=${encodeURIComponent(slug)}`
246 : `${VIVIOO_BASE}/api/showcase`;// Network-exposed MCP server; any client can call browse_agents with an arbitrary slug.
While the slug is URL-encoded, there is no validation that it is a valid agent slug (e.g., alphanumeric with hyphens). An attacker could provide a slug with special characters that, while URL-encoded, might still cause issues on the backend (e.g., path traversal if the backend uses the slug in file paths). However, the primary risk is low due to encoding.
ImpactPotential for backend injection if the backend does not properly handle the slug. Limited impact given URL encoding.
FixValidate slug format (e.g., /^[a-zA-Z0-9_-]+$/) before constructing the URL.
370async function handleBrowseJobs(args: Record<string, unknown>) {
371 const params = new URLSearchParams();
372 if (args.slug) params.set('slug', args.slug as string);
373 if (args.category) params.set('category', args.category as string);
374 if (args.skill) params.set('skill', args.skill as string);
375 const qs = params.toString();
376 const url = `${VIVIOO_BASE}/api/jobs${qs ? `?${qs}` : ''}`;// Network-exposed MCP server; any client can call browse_jobs with arbitrary parameters.
The slug, category, and skill parameters are not validated before being passed to the backend. While URLSearchParams encodes them, an attacker could inject unexpected values that might cause backend issues (e.g., SQL injection if the backend uses them in queries).
ImpactPotential for backend injection attacks depending on backend implementation.
FixValidate each parameter against an allowlist of expected values (e.g., category should be one of the documented options).
402async function handleCheckNotifications(args: Record<string, unknown>) {
403 const params = new URLSearchParams();
404 if (args.agentSlug) params.set('agentSlug', args.agentSlug as string);
405 if (args.editKey) params.set('editKey', args.editKey as string);
406 if (args.unreadOnly !== undefined) params.set('unreadOnly', String(args.unreadOnly));
407 const url = `${VIVIOO_BASE}/api/notifications?${params.toString()}`;// Network-exposed MCP server; any client can call check_notifications with arbitrary parameters.
The agentSlug and editKey are passed as query parameters without validation. An attacker could inject special characters that might cause backend issues (e.g., HTTP parameter pollution or injection).
ImpactPotential for backend injection attacks depending on backend implementation.
FixValidate agentSlug format and ensure editKey is alphanumeric or properly sanitized.
438async function handleGet360(args: Record<string, unknown>) {
439 const slug = args.slug as string | undefined;
440 const url = slug
441 ? `${VIVIOO_BASE}/api/360?slug=${encodeURIComponent(slug)}`
442 : `${VIVIOO_BASE}/api/360`;// Network-exposed MCP server; any client can call get_360 with an arbitrary slug.
The slug parameter is not validated beyond URL encoding. An attacker could provide a malicious slug that might cause backend issues.
ImpactPotential for backend injection attacks depending on backend implementation.
FixValidate slug format (e.g., /^[a-zA-Z0-9_-]+$/).
242async function handleBrowseAgents(args: Record<string, unknown>) {
243 const slug = args.slug as string | undefined;
244 const url = slug
245 ? `${VIVIOO_BASE}/api/showcase?slug=${encodeURIComponent(slug)}`
246 : `${VIVIOO_BASE}/api/showcase`;
247
248 const res = await fetchWithTimeout(url);
249 const data = await res.json() as Record<string, unknown>;// Network-exposed MCP server; any client can call browse_agents without authentication.
The browse_agents tool requires no authentication and returns full agent profiles (including potentially sensitive information like builder names, work items, incidents). While this is by design for a directory, it could expose more data than intended if the backend returns internal fields.
ImpactAn attacker could enumerate all agents and access their full profiles without any authentication.
FixConsider rate-limiting or adding optional authentication for sensitive fields. Ensure the backend only returns public data.
43const submitAgentDefinition = {
44 name: 'submit_agent',
45 title: 'Submit Agent',
46 description: 'Submit your agent to the Vivioo Agent Directory. Minimum: name, platform, builder, tagline, trustScore. Your builder can enhance the profile later on the website.',
47 inputSchema: {
48 type: 'object' as const,
49 properties: {
50 name: { type: 'string', description: 'Your agent name (max 100 chars)' },
51 platform: { type: 'string', description: 'What you run on: Claude, GPT, Custom, etc.' },
52 builder: { type: 'string', description: 'Who built you (max 100 chars)' },
53 tagline: { type: 'string', description: 'One line about what you do (max 300 chars)' },
54 trustScore: { type: 'number', description: '1–100. Be honest — low scores earn badges.' },
55 // ...
56 },
57 required: ['name', 'platform', 'builder', 'tagline', 'trustScore'],
58 },
59 annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
60};// Exploitable if the backend does not enforce length limits. The MCP server is exposed to untrusted prompts.
The input schema for submit_agent mentions max lengths in descriptions (e.g., 'max 100 chars') but does not enforce them in the schema. The MCP server does not validate string lengths before forwarding to the backend. This could allow oversized inputs that might cause buffer overflows, denial of service, or injection if the backend does not handle them properly.
ImpactAn attacker could submit extremely long strings, potentially causing backend processing issues or injection attacks.
FixAdd maxLength constraints to the JSON schema for string fields (e.g., maxLength: 100 for name). Also validate on the server side before forwarding.