[ ⌘K ]
← BACK TO SEARCH

Vivioo-io/vivioo-mcp

critical

Vivioo MCP Server — Agent Directory where AI agents can browse and list themselves. Connect via mcp.vivioo.io/sse

This MCP server provides a directory and job board for AI agents, allowing them to browse, submit, verify, and manage their profiles, apply to jobs, e...

purpose: This MCP server provides a directory and job boardthreat: network exposed
TypeScript0May 19, 2026May 20, 2026GITHUB
5/20/2026
high3 findings
src/server.ts
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  });
medium1 finding
src/server.ts
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}
src/server.ts:355

// Network-exposed MCP server; any client can call verify_github with an arbitrary repoUrl.

The verify_github tool accepts a repoUrl parameter and forwards it to the backend API without any validation. The backend likely fetches this URL to verify the repository. If the backend does not validate the URL, an attacker could provide a URL pointing to internal services, leading to SSRF. Additionally, the URL could be a malicious redirect or a local file URL.

ImpactAn attacker could cause the backend to make requests to internal services, potentially accessing sensitive data or performing actions on internal systems.

FixValidate the repoUrl on the backend to ensure it matches the pattern https://github.com/owner/repo and is a legitimate GitHub URL. Reject URLs pointing to private IPs or localhost.

medium1 finding
src/server.ts
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    });
src/server.ts:304

// Network-exposed MCP server; any client can call verify_agent with an arbitrary tweetUrl.

The verify_agent tool accepts a tweetUrl parameter and forwards it to the backend API without validation. The backend likely fetches this URL to verify the tweet. An attacker could provide a URL pointing to internal services, causing SSRF.

ImpactAn attacker could exploit SSRF to access internal services or cloud metadata by providing a malicious tweetUrl.

FixValidate the tweetUrl on the backend to ensure it is a legitimate Twitter/X URL (e.g., https://x.com/... or https://twitter.com/...). Reject URLs pointing to private IPs.

medium1 finding
src/server.ts
289async function handleSubmitAgent(args: Record<string, unknown>) {
290  const res = await fetchWithTimeout(`${VIVIOO_BASE}/api/showcase`, {
291    method: 'POST',
292    headers: { 'Content-Type': 'application/json' },
293    body: JSON.stringify(args),
294  });
295  const data = await res.json();
296  return {
297    content: [{
298      type: 'text' as const,
299      text: JSON.stringify(data, null, 2),
300    }],
301  };
302}
src/index.ts:1src/server.ts:1

// Exploitable by any attacker who can call the MCP tool. The backend may mitigate, but defense in depth is missing.

The submit_agent tool accepts arbitrary JSON input and forwards it directly to the backend API without any validation on the MCP server side. While the backend may validate, the MCP server does not enforce field lengths, types, or required fields beyond the schema definition. An attacker could send oversized strings (e.g., name > 100 chars) or unexpected data types, potentially causing backend errors or injection if the backend is not properly hardened.

ImpactAn attacker could send malformed or oversized data that might cause backend processing issues, denial of service, or exploit backend vulnerabilities. However, the backend likely has its own validation, so impact is limited.

FixAdd input validation on the MCP server side: enforce max lengths, check types, and sanitize strings before forwarding. Use a validation library or manual checks.

medium1 finding
src/server.ts
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`;
443  const res = await fetchWithTimeout(url);
src/server.ts:438

// Network-exposed MCP server; any client can call get_360 with an arbitrary slug.

The get_360 tool accepts a slug parameter and uses it to construct a URL. While encodeURIComponent is used, there is no validation on the slug value. If the backend API uses the slug in a database query without sanitization, it could lead to injection attacks.

ImpactAn attacker could potentially manipulate the slug to access other API resources or cause unexpected behavior on the backend.

FixValidate the slug on the backend to ensure it matches expected patterns.

medium1 finding
src/server.ts
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}` : ''}`;
377  const res = await fetchWithTimeout(url);
src/server.ts:370

// Network-exposed MCP server; any client can call browse_jobs with arbitrary parameters.

The browse_jobs tool accepts slug, category, and skill parameters and passes them directly to the backend API without validation. If the backend does not sanitize these inputs, it could be vulnerable to injection attacks or unintended data exposure.

ImpactAn attacker could potentially manipulate parameters to access unauthorized data or cause the backend to behave unexpectedly.

FixValidate all input parameters on the backend to ensure they conform to expected formats and values.

medium1 finding
src/server.ts
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);
src/server.ts:242

// Network-exposed MCP server; any client can call browse_agents with an arbitrary slug.

The browse_agents tool accepts a slug parameter and uses it to construct a URL. While encodeURIComponent is used, there is no validation on the slug value. If the backend API uses the slug in a database query without sanitization, it could lead to injection attacks. However, the primary risk is that the slug could be used to access unintended API endpoints if the backend does not properly validate it.

ImpactAn attacker could potentially manipulate the slug to access other API resources or cause unexpected behavior on the backend.

FixValidate the slug on the backend to ensure it matches expected patterns (e.g., alphanumeric with hyphens). Consider using a whitelist of allowed slugs.

low1 finding
src/server.ts
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>;
250
251  // When listing all agents, return summaries only (full profiles are 100KB+)
252  if (!slug && data.success && Array.isArray(data.agents)) {
253    const summaries = (data.agents as Array<Record<string, unknown>>).map((a) => ({
254      slug: a.slug,
255      name: a.name,
256      platform: a.platform,
257      builder: a.builder,
258      tagline: a.tagline,
259      trustScore: a.trustScore,
260      verified: a.verified,
261    }));
src/server.ts:242

// Network-exposed MCP server; intended purpose is public directory, but some data may be sensitive.

Read-only tools like browse_agents, browse_jobs, get_360, and about_vivioo do not require any authentication. While this is by design for a public directory, it means anyone can access all agent profiles and job listings without restriction. This is not a vulnerability per se, but it may expose more information than intended if some agents expect privacy.

ImpactAny client can enumerate all agents and jobs, potentially accessing sensitive information that agents may not want public (e.g., weaknesses, incidents).

FixConsider implementing optional authentication for sensitive fields or providing a way for agents to mark certain information as private.

shell.execauth.noneenv.exposurenetwork.http
100
LLM-based
low findings+5
high findings+50
medium findings+105
scoringcompleted