visbridge-mcp

mcp
Guvenlik Denetimi
Uyari
Health Gecti
  • License — License: Apache-2.0
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 55 GitHub stars
Code Uyari
  • fs module — File system access in package.json
  • process.env — Environment variable access in src/config.ts
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

A token-efficient, provider-agnostic vision layer for reasoning models via MCP.

README.md

visbridge-mcp

npm version
license

Status: stable beta. The core pipeline (media resolution, SSRF hardening, provider
adapters, local preprocessing) is covered by automated tests and releases are automated,
but real-world mileage is still limited. For local / personal use it is production-grade;
for critical workflows, pin your version and smoke-test with visbridge-mcp doctor first.

Token-efficient vision for MCP clients. An MCP server
(stdio + Streamable HTTP) exposing a single analyze_image tool that lets any MCP client see
images — describe, OCR, or inspect — through an OpenAI-compatible vision provider.

It exists for one reason: to give a model without native vision (or any client that wants to
spend fewer tokens) the ability to understand images. The client sends only an image reference
plus a mode; the server resolves the image, sends it to the vision model, and returns compact plain
text.

┌──────────────────┐    JSON-RPC (stdio)   ┌──────────────────┐     HTTP (chat)     ┌──────────────────┐
│    MCP client    │ ────────────────────▶ │   visbridge-mcp   │ ──────────────────▶ │  vision provider │
│   (non-vision    │ ◀──────────────────── │   (this server)   │ ◀────────────────── │  (OpenAI-compat, │
│     model)       │    compact text       │                   │   /chat/completions │    e.g. gemini)  │
└──────────────────┘                       └──────────────────┘                     └──────────────────┘

Features

  • Two transports — stdio for local clients (Claude Code, Cursor), Streamable HTTP (POST /mcp)
    for local clients that connect over HTTP. Both run on the user's own machine — this server is
    not designed for remote/public hosting.
  • One tool, three modesdescribe (summarize), ocr (extract text), inspect (answer a
    specific question via prompt).
  • detail: low by default — cheaper and measurably better OCR than high.
  • Fence-free OCR — every OCR answer is stripped of markdown code fences defensively.
  • Four image sources — local path, url, base64 (data URL or raw), and resource
    (resolved against a configured directory).
  • Hardened — SSRF protection (private/loopback/link-local IPs blocked), DNS resolved before
    fetch, MIME sniffing (PNG/JPEG/WebP/GIF) + size limits, at most one redirect.
  • Local preprocessing — oversized images are downscaled before the provider call
    (VISION_MAX_DIMENSION, default 2048, never upscales; animated GIFs use the first frame).
    Cheaper, faster, and immune to provider-side image limits. A warnings entry notes every resize.
  • Structured errors — 10-code taxonomy, MCP-safe messages (no keys/URLs leak).
  • Observability — token/cost estimation and one JSON log line per call, written to stderr
    (never stdout, which is reserved for the JSON-RPC channel).

Requirements

  • Node.js >= 20
  • A vision provider exposing an OpenAI-compatible POST /chat/completions endpoint + an API key.

Setup

npm install
cp .env.example .env   # then fill in VISION_BASE_URL / VISION_API_KEY / VISION_MODEL

The server reads process.env directly. If you register it in an MCP client, the client populates
the environment from its own config — a .env file is optional.

Configuration

Variable Required Default Description
VISION_BASE_URL Base URL of the OpenAI-compatible endpoint (http/https).
VISION_API_KEY Bearer token sent to the provider.
VISION_MODEL Vision model id, e.g. ag/gemini-3.6-flash-medium.
VISION_PROVIDER openai-compatible Adapter: openai-compatible (chat completions) or mock (offline deterministic adapter for tests/demos). The openai Responses API adapter is not implemented yet.
VISION_TRANSPORT stdio Transport: stdio (default) or http (Streamable HTTP).
VISION_HTTP_HOST 127.0.0.1 Bind address for the HTTP transport.
VISION_HTTP_PORT 3000 Port for the HTTP transport.
VISION_DESCRIBE_OUTPUT_BUDGET 256 Max output tokens for describe.
VISION_INSPECT_OUTPUT_BUDGET 384 Max output tokens for inspect.
VISION_OCR_OUTPUT_BUDGET 1024 Max output tokens for ocr.
VISION_TIMEOUT_MS 60000 Provider request timeout (ms).
VISION_MAX_RETRIES 2 Retries for transient errors (429 / 5xx / network); honours retry-after. 0 disables.
VISION_MAX_IMAGE_BYTES 20971520 Max decoded image size (20 MB).
VISION_MAX_DIMENSION 2048 Downscale locally so the longest edge fits (never upscales; 0 disables).
VISION_MAX_REDIRECTS 3 Max redirects for url images; every hop is SSRF-revalidated.
VISION_SSRF_ALLOW_HOSTS (none) Comma-separated hostnames allowed to reach private networks.
VISION_RESOURCE_DIR (none) Base dir for resolving resource image references.

Usage

Install from npm (npx)

The package is published to npm — anyone can run it without a local checkout:

{
  "mcpServers": {
    "visbridge": {
      "command": "npx",
      "args": ["-y", "visbridge-mcp"],
      "env": {
        "VISION_BASE_URL": "https://...",
        "VISION_API_KEY": "sk-...",
        "VISION_MODEL": "ag/gemini-3.6-flash-medium"
      }
    }
  }
}

With npx there is no local .env or launcher script, so credentials come from the client's
env block (above) rather than a .env file.

Register in Claude Code

Published package, stdio (credentials via -e flags):

claude mcp add visbridge \
  -e VISION_BASE_URL=https://... \
  -e VISION_API_KEY=sk-... \
  -e VISION_MODEL=ag/gemini-3.6-flash-medium \
  -- npx -y visbridge-mcp

HTTP transport (local clients that connect over HTTP):

claude mcp add --transport http visbridge http://127.0.0.1:3000/mcp

Local development (from a checkout): point at the launcher instead, so the API key stays in
the repo's .env rather than the client config:
claude mcp add visbridge -- /path/to/visbridge-mcp/scripts/visbridge.sh

Verify with claude mcp list, then in a session ask the (non-vision) model to use the tool:

Use analyze_image to describe fixtures/images/dense-ui.png
Use analyze_image in ocr mode to read all the text in this image
Use analyze_image: what is the background color of the hero section, and how many CTAs are there?

Register in Cursor

Create .cursor/mcp.json:

{
  "mcpServers": {
    "visbridge": {
      "command": "npx",
      "args": ["-y", "visbridge-mcp"],
      "env": {
        "VISION_BASE_URL": "https://...",
        "VISION_API_KEY": "sk-...",
        "VISION_MODEL": "ag/gemini-3.6-flash-medium"
      }
    }
  }
}

For the HTTP transport, replace command/args/env with
"url": "http://127.0.0.1:3000/mcp".

Register in Kilo Code

Kilo Code stores MCP servers in ~/.config/kilo/kilo.jsonc (JSONC), under a top-level mcp key.
Its schema is not the standard mcpServers shape — command is the full argument array (no
separate args) and enabled is required:

{
  "mcp": {
    "visbridge": {
      "type": "local",
      "command": ["npx", "-y", "visbridge-mcp"],
      "environment": {
        "VISION_BASE_URL": "https://...",
        "VISION_API_KEY": "sk-...",
        "VISION_MODEL": "ag/gemini-3.6-flash-medium"
      },
      "enabled": true
    }
  }
}

For the HTTP transport, replace type/command/environment with "type": "http" and
"url": "http://127.0.0.1:3000/mcp" — pointing at a server running on the same machine.

Other clients (Windsurf, VS Code, Cline, …)

They accept the standard mcpServers shape from the Cursor example above. Two notes:

  • HTTP transport — replace command/args/env with "url": "http://127.0.0.1:3000/mcp"
    (a server running on the same machine).
  • Windows — wrap the stdio command: "command": "cmd", "args": ["/c", "npx", "-y", "visbridge-mcp"].

The three VISION_* variables are the only required config; everything else has safe defaults.

Invoke the tool directly

analyze_image accepts:

{
  "image": { "kind": "path", "value": "fixtures/images/dense-ui.png" }, // path | url | base64 | resource
  "mode": "auto",          // auto | describe | ocr | inspect
  "prompt": "…",           // optional; for inspect mode
  "context": "…",          // optional; the user's original request so the vision model understands intent
  "detail": "auto"         // auto | low | high
}

Mode resolution: autodescribe when prompt is empty, otherwise inspect. Detail resolution:
autolow.

context — when set, the user's original question or intent is injected into the vision model's
prompt alongside the mode-specific instruction. This lets the vision model focus on what the user
actually asked about (e.g. "analyze the layout", "find the error code") even when the driving
model only describes the mode generically. Always pass the full user request as context when the
driving model's own instructions risk stripping the intent.

The result is a structured object plus a human-readable text block:

{
  "answer": "…",          // always present
  "text": "…",            // for ocr (plain text, fence-free)
  "warnings": ["…"],      // optional
  "truncated": false      // true if the provider hit the output budget
}

Run over HTTP (Streamable HTTP)

Some local clients and web UIs prefer to connect over HTTP instead of stdio. Run the server over
Streamable HTTP on the same machine:

VISION_TRANSPORT=http VISION_HTTP_PORT=3000 node --import tsx src/index.ts

The server listens on VISION_HTTP_HOST:VISION_HTTP_PORT (default 127.0.0.1:3000):

  • POST /mcp — the MCP endpoint (stateless, JSON responses).
  • GET /health — liveness probe.

Register it in a client that supports HTTP MCP servers:

claude mcp add --transport http visbridge http://127.0.0.1:3000/mcp

Local-only by design. This server is meant to run on the user's own machine and has no
built-in authentication. Do not bind VISION_HTTP_HOST=0.0.0.0 or expose the port beyond
localhost — the HTTP transport carries the provider's VISION_API_KEY, accepts arbitrary
local path image references, and would let anyone who can reach the port use your provider
account. If you ever need remote access, front it with an authenticated TLS gateway.

Discoverability — making non-vision models use it reliably

The tool's description is written to signal when to use it, and most models will reach for
analyze_image on their own. But non-vision models have a trained-in reflex to refuse image
requests ("I can't see images"), and some paste attachments arrive as clipboard-*.png
filenames that invite wrong guesses. To make the behavior deterministic, add the ready-made
global rules from docs/agents-image-rules.md to your
client's instructions:

  • Claude Code, global~/.claude/CLAUDE.md
  • Claude Code, per-projectCLAUDE.md
  • Claude Code, per-agent.claude/agents/<name>.md
  • Cursor → a rule under .cursor/rules/
  • opencode~/.config/opencode/AGENTS.md (global) or AGENTS.md in the project

The rules cover: calling the tool in the same turn (no refusals, no hedged openers), mapping
pasted data URLs to kind: "base64" instead of inventing paths, mode selection, and error
reporting. They were validated against GLM 5.3 and DeepSeek V4 (text-only) driving opencode.

CLI

visbridge-mcp              start the stdio MCP server   (default)
visbridge-mcp init         print a ready-to-paste client config
visbridge-mcp config       print effective (redacted) configuration
visbridge-mcp doctor [img] run a live describe + ocr against an image

Run without building (dev):

npx tsx src/index.ts doctor        # live smoke test with your real credentials
npm run demo                       # MCP-client demo: listTools + describe + ocr

Development

npm run typecheck   # tsc --noEmit
npm test            # 93 unit + integration tests (mock provider + local HTTP fakes; no network)
npm run build       # compile to dist/  (bin: visbridge-mcp)
npm run dev         # start the stdio server via tsx

Try the full pipeline with no credentials at all:

VISION_PROVIDER=mock npx tsx src/index.ts doctor   # offline smoke test of the pipeline

Security

  • SSRF: URL images resolve DNS first, validate every address, block private, loopback,
    link-local, and unspecified ranges (IPv4 + IPv6), and pin the connection to the validated
    addresses (defeats DNS rebinding). Redirects are bounded (VISION_MAX_REDIRECTS) and each hop
    is re-validated. Downloads are streamed with a hard byte cap — a lying content-length cannot
    buffer an unbounded response. Override per-host via VISION_SSRF_ALLOW_HOSTS.
  • No secret leakage: API keys are masked in config output and never appear in logs or error
    messages. The launcher script keeps the key out of client config files.
  • Stdout is the protocol channel — all logging goes to stderr.

Publishing to npm

Releases are automated with npm trusted publishing
(GitHub Actions OIDC — no npm token). To cut a release:

npm version patch          # or minor / major — bumps version, commits, and tags
git push --follow-tags

Pushing the v* tag triggers .github/workflows/publish.yml, which runs the tests, builds, and
publishes to npm. The tarball ships only package.json, README.md, LICENSE, and dist/ — no
src/, fixtures, or .env.

Roadmap

  • resource wired to a live MCP resource list.
  • openai.ts Responses-API adapter (native OpenAI path, replacing openai-compatible where desired).
  • Richer doctor output (surfaced per-call cost).

This project is local-only — there are no plans for remote/multi-tenant hosting, so HTTP
authorization is explicitly out of scope.

Yorumlar (0)

Sonuc bulunamadi