Skip to main content
Glama
VisualSentinel

Visual Sentinel MCP Server

Official

vs_monitors_create

Create a new monitor for a website or service. Provide name, URL, and type to get the monitor configured and its ID returned.

Instructions

Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYesDisplay name for the monitor.
urlYesFull URL to monitor (or hostname for PING/PORT/DNS/SSL).
typeYesMonitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.
intervalNoCheck interval in seconds (60, 300, 600, 1800, 3600).
timeoutNoPer-check timeout in seconds (default 30).
methodNoHTTP method for HTTP/HTTPS monitors (default GET).
keywordNoKeyword to assert for KEYWORD monitors.
portNoTCP port for PORT monitors.

Implementation Reference

  • Handler function that executes the 'vs_monitors_create' tool. Sends a POST request to /api/monitors with name, url, type (required) and optional interval, timeout, method, keyword, port.
    handler: async (args, client) =>
      client.request('POST', '/api/monitors', {
        body: {
          name: requireString(args, 'name'),
          url: requireString(args, 'url'),
          type: requireString(args, 'type'),
          interval: pickNumber(args, 'interval'),
          timeout: pickNumber(args, 'timeout'),
          method: pickString(args, 'method'),
          keyword: pickString(args, 'keyword'),
          port: pickNumber(args, 'port'),
        },
      }),
  • Input schema for 'vs_monitors_create'. Defines required fields (name, url, type) and optional fields (interval, timeout, method, keyword, port) with descriptions.
    {
      name: 'vs_monitors_create',
      description:
        'Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.',
      inputSchema: {
        type: 'object',
        properties: {
          name: { ...STR, description: 'Display name for the monitor.' },
          url: { ...STR, description: 'Full URL to monitor (or hostname for PING/PORT/DNS/SSL).' },
          type: {
            ...STR,
            description:
              'Monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',
          },
          interval: { ...INT, description: 'Check interval in seconds (60, 300, 600, 1800, 3600).' },
          timeout: { ...INT, description: 'Per-check timeout in seconds (default 30).' },
          method: { ...STR, description: 'HTTP method for HTTP/HTTPS monitors (default GET).' },
          keyword: { ...STR, description: 'Keyword to assert for KEYWORD monitors.' },
          port: { ...INT, description: 'TCP port for PORT monitors.' },
        },
        required: ['name', 'url', 'type'],
        additionalProperties: false,
      },
  • src/tools.ts:48-396 (registration)
    The tool is registered as an entry in the TOOLS array (line 48). vs_monitors_create is at index position in the array. The array is exported and used by index.ts to register all tools with the MCP SDK.
    export const TOOLS: ToolDefinition[] = [
      // -------------------------------------------------------------------------
      // Public tools (no auth)
      // -------------------------------------------------------------------------
      {
        name: 'vs_health',
        description:
          'Check whether Visual Sentinel itself is up. Returns the service health status. No authentication required.',
        inputSchema: {
          type: 'object',
          properties: {},
          additionalProperties: false,
        },
        requiresAuth: false,
        handler: async (_args, client) => client.request('GET', '/api/health', { auth: false }),
      },
    
      {
        name: 'vs_dns_check',
        description:
          'Resolve DNS records (A, AAAA, MX, NS, TXT, CNAME) for a domain using Visual Sentinel\'s public DNS lookup tool. No authentication required.',
        inputSchema: {
          type: 'object',
          properties: {
            domain: { ...STR, description: 'Domain to resolve, e.g. example.com (without protocol).' },
            recordType: {
              ...STR,
              description: 'Optional: limit to one record type. One of: A, AAAA, MX, NS, TXT, CNAME, SOA. Default: all.',
            },
          },
          required: ['domain'],
          additionalProperties: false,
        },
        requiresAuth: false,
        handler: async (args, client) =>
          client.request('GET', '/api/tools/dns-check', {
            auth: false,
            query: {
              domain: requireString(args, 'domain'),
              recordType: pickString(args, 'recordType'),
            },
          }),
      },
    
      {
        name: 'vs_ssl_check',
        description:
          'Inspect a TLS/SSL certificate for a hostname: issuer, subject, validity dates, SAN list, key algorithm, and certificate chain. No authentication required.',
        inputSchema: {
          type: 'object',
          properties: {
            host: { ...STR, description: 'Hostname to inspect, e.g. example.com (without protocol).' },
            port: { ...INT, description: 'TCP port. Default 443.' },
          },
          required: ['host'],
          additionalProperties: false,
        },
        requiresAuth: false,
        handler: async (args, client) =>
          client.request('GET', '/api/tools/ssl-check', {
            auth: false,
            query: { host: requireString(args, 'host'), port: pickNumber(args, 'port') },
          }),
      },
    
      {
        name: 'vs_speed_test',
        description:
          'Run a one-shot performance check against a URL: TTFB, total load time, transfer size, status code, and redirect chain. No authentication required.',
        inputSchema: {
          type: 'object',
          properties: {
            url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },
          },
          required: ['url'],
          additionalProperties: false,
        },
        requiresAuth: false,
        handler: async (args, client) =>
          client.request('GET', '/api/tools/speed-test', {
            auth: false,
            query: { url: requireString(args, 'url') },
          }),
      },
    
      {
        name: 'vs_website_check',
        description:
          'Quick health check for a URL: HTTP status, response time, server header, content snippet, and basic SSL state. No authentication required.',
        inputSchema: {
          type: 'object',
          properties: {
            url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },
          },
          required: ['url'],
          additionalProperties: false,
        },
        requiresAuth: false,
        handler: async (args, client) =>
          client.request('GET', '/api/tools/website-check', {
            auth: false,
            query: { url: requireString(args, 'url') },
          }),
      },
    
      // -------------------------------------------------------------------------
      // Authenticated tools (require VS_API_KEY)
      // -------------------------------------------------------------------------
      {
        name: 'vs_monitors_list',
        description:
          'List monitors in the authenticated organization. Optional filters narrow by status, type, or paginate.',
        inputSchema: {
          type: 'object',
          properties: {
            status: { ...STR, description: 'Filter: UP, DOWN, PAUSED, MAINTENANCE.' },
            type: {
              ...STR,
              description:
                'Filter by monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',
            },
            page: { ...INT, description: 'Page number (default 1).' },
            limit: { ...INT, description: 'Items per page (default 50, max 200).' },
          },
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('GET', '/api/monitors', {
            query: {
              status: pickString(args, 'status'),
              type: pickString(args, 'type'),
              page: pickNumber(args, 'page'),
              limit: pickNumber(args, 'limit'),
            },
          }),
      },
    
      {
        name: 'vs_monitors_get',
        description:
          'Fetch a single monitor by id, including its current status, last check, and configuration.',
        inputSchema: {
          type: 'object',
          properties: {
            id: { ...STR, description: 'Monitor id (cuid).' },
          },
          required: ['id'],
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('GET', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}`),
      },
    
      {
        name: 'vs_monitors_create',
        description:
          'Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.',
        inputSchema: {
          type: 'object',
          properties: {
            name: { ...STR, description: 'Display name for the monitor.' },
            url: { ...STR, description: 'Full URL to monitor (or hostname for PING/PORT/DNS/SSL).' },
            type: {
              ...STR,
              description:
                'Monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',
            },
            interval: { ...INT, description: 'Check interval in seconds (60, 300, 600, 1800, 3600).' },
            timeout: { ...INT, description: 'Per-check timeout in seconds (default 30).' },
            method: { ...STR, description: 'HTTP method for HTTP/HTTPS monitors (default GET).' },
            keyword: { ...STR, description: 'Keyword to assert for KEYWORD monitors.' },
            port: { ...INT, description: 'TCP port for PORT monitors.' },
          },
          required: ['name', 'url', 'type'],
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('POST', '/api/monitors', {
            body: {
              name: requireString(args, 'name'),
              url: requireString(args, 'url'),
              type: requireString(args, 'type'),
              interval: pickNumber(args, 'interval'),
              timeout: pickNumber(args, 'timeout'),
              method: pickString(args, 'method'),
              keyword: pickString(args, 'keyword'),
              port: pickNumber(args, 'port'),
            },
          }),
      },
    
      {
        name: 'vs_monitors_check_now',
        description:
          'Trigger an immediate check for a monitor (in addition to its scheduled cadence). Returns the freshly-collected check result.',
        inputSchema: {
          type: 'object',
          properties: {
            id: { ...STR, description: 'Monitor id.' },
          },
          required: ['id'],
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('POST', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/check`),
      },
    
      {
        name: 'vs_monitors_uptime',
        description:
          'Fetch the uptime percentage and outage breakdown for a monitor over a window (default last 30 days).',
        inputSchema: {
          type: 'object',
          properties: {
            id: { ...STR, description: 'Monitor id.' },
            window: {
              ...STR,
              description: 'Window: 24h, 7d, 30d, 90d, 365d. Default 30d.',
            },
          },
          required: ['id'],
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('GET', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/uptime`, {
            query: { window: pickString(args, 'window') },
          }),
      },
    
      {
        name: 'vs_incidents_list',
        description:
          'List incidents for the authenticated organization. Filter by status (OPEN/RESOLVED), monitor id, or paginate.',
        inputSchema: {
          type: 'object',
          properties: {
            status: { ...STR, description: 'OPEN or RESOLVED.' },
            monitorId: { ...STR, description: 'Filter to one monitor.' },
            page: { ...INT },
            limit: { ...INT, description: 'Default 50, max 200.' },
          },
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('GET', '/api/incidents', {
            query: {
              status: pickString(args, 'status'),
              monitorId: pickString(args, 'monitorId'),
              page: pickNumber(args, 'page'),
              limit: pickNumber(args, 'limit'),
            },
          }),
      },
    
      {
        name: 'vs_incidents_get',
        description: 'Fetch a single incident with its full check history and root-cause hints.',
        inputSchema: {
          type: 'object',
          properties: {
            id: { ...STR, description: 'Incident id.' },
          },
          required: ['id'],
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('GET', `/api/incidents/${encodeURIComponent(requireString(args, 'id'))}`),
      },
    
      {
        name: 'vs_alerts_list',
        description:
          'List alerts for the authenticated organization. Filter by status (UNACKNOWLEDGED/ACKNOWLEDGED/RESOLVED) or paginate.',
        inputSchema: {
          type: 'object',
          properties: {
            status: { ...STR, description: 'UNACKNOWLEDGED, ACKNOWLEDGED, or RESOLVED.' },
            page: { ...INT },
            limit: { ...INT, description: 'Default 50, max 200.' },
          },
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('GET', '/api/alerts', {
            query: {
              status: pickString(args, 'status'),
              page: pickNumber(args, 'page'),
              limit: pickNumber(args, 'limit'),
            },
          }),
      },
    
      {
        name: 'vs_alerts_acknowledge',
        description: 'Acknowledge an alert by id. Acknowledgement is recorded with the calling API key\'s user.',
        inputSchema: {
          type: 'object',
          properties: {
            id: { ...STR, description: 'Alert id.' },
            note: { ...STR, description: 'Optional acknowledgement note (visible in alert history).' },
          },
          required: ['id'],
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('POST', `/api/alerts/${encodeURIComponent(requireString(args, 'id'))}/acknowledge`, {
            body: { note: pickString(args, 'note') },
          }),
      },
    
      {
        name: 'vs_status_pages_list',
        description: 'List public-facing status pages owned by the authenticated organization.',
        inputSchema: {
          type: 'object',
          properties: {},
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (_args, client) => client.request('GET', '/api/status-pages'),
      },
    
      {
        name: 'vs_servers_list',
        description: 'List servers (origins) registered in the authenticated organization.',
        inputSchema: {
          type: 'object',
          properties: {
            page: { ...INT },
            limit: { ...INT, description: 'Default 50.' },
          },
          additionalProperties: false,
        },
        requiresAuth: true,
        handler: async (args, client) =>
          client.request('GET', '/api/servers', {
            query: { page: pickNumber(args, 'page'), limit: pickNumber(args, 'limit') },
          }),
      },
    ];
  • Helper functions used by the handler: pickNumber/pickString (extract optional args) and requireString (validate required args).
    function pickString(args: Record<string, unknown>, key: string): string | undefined {
      const v = args[key];
      return typeof v === 'string' && v.length > 0 ? v : undefined;
    }
    
    function pickNumber(args: Record<string, unknown>, key: string): number | undefined {
      const v = args[key];
      return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
    }
    
    function requireString(args: Record<string, unknown>, key: string): string {
  • The ApiClient.request method that the handler calls. Sends HTTP requests to the Visual Sentinel API with optional auth headers, query params, and JSON body.
      async request<T = unknown>(
        method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
        path: string,
        options: { query?: Record<string, string | number | boolean | undefined>; body?: unknown; auth?: boolean } = {},
      ): Promise<T> {
        const auth = options.auth ?? true;
        if (auth && !this.hasApiKey()) {
          throw new VsApiError(
            'Visual Sentinel API key required. Set VS_API_KEY (https://visualsentinel.com/settings/api-keys).',
            401,
            null,
          );
        }
    
        const url = new URL(path.startsWith('/') ? path : `/${path}`, this.baseUrl);
        if (options.query) {
          for (const [k, v] of Object.entries(options.query)) {
            if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
          }
        }
    
        const headers: Record<string, string> = {
          Accept: 'application/json',
          'User-Agent': this.userAgent,
        };
        if (auth && this.apiKey) headers['X-API-Key'] = this.apiKey;
        if (options.body !== undefined) headers['Content-Type'] = 'application/json';
    
        const res = await fetch(url, {
          method,
          headers,
          body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
        });
    
        const text = await res.text();
        let parsed: unknown = null;
        if (text.length > 0) {
          try {
            parsed = JSON.parse(text);
          } catch {
            parsed = text;
          }
        }
    
        if (!res.ok) {
          throw new VsApiError(
            `Visual Sentinel API ${method} ${path} returned ${res.status}`,
            res.status,
            parsed,
          );
        }
    
        return parsed as T;
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must convey behavioral traits. It only states that it creates a monitor and returns an object with an ID. It does not disclose permissions, idempotency, rate limits, or side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is very concise: two sentences that front-load the action and required parameters. No unnecessary words or redundancies.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 8 parameters, no annotations, and no output schema, the description is too minimal. It does not explain error conditions, how to use optional parameters like interval or timeout, or what the returned object contains beyond the ID. More context is needed for a creation tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage for all 8 parameters. The description adds no extra meaning beyond 'Provide at least name, url, and type.' Since schema coverage is high, a baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Create a new monitor' with required parameters name, url, and type, and mentions it returns the created monitor with its ID. This distinguishes it from sibling tools like vs_monitors_list or vs_monitors_get.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides minimal guidance: only the required parameters. It does not specify when to use this tool versus alternatives, nor mention any prerequisites or exclusions. No context about when not to use it is given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/VisualSentinel/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server