Skip to content

feat(vision-mcp): add local-first vision engine and MCP server - #1271

Draft
ARDA7787 wants to merge 15 commits into
roboflow:developfrom
ARDA7787:feat/vision-mcp-server
Draft

feat(vision-mcp): add local-first vision engine and MCP server#1271
ARDA7787 wants to merge 15 commits into
roboflow:developfrom
ARDA7787:feat/vision-mcp-server

Conversation

@ARDA7787

@ARDA7787 ARDA7787 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

This PR adds vision-mcp/, a standalone, local-first MCP integration for RF-DETR. It gives AI agents structured access to live vision workflows without modifying the existing RF-DETR package.

The feature is split into two processes:

  • vision-engine: a long-running local daemon that owns camera streams, model loading, inference, tracking, SQLite metrics, and generated evidence artifacts. Its HTTP API is bound to 127.0.0.1.
  • vision-mcp: a lightweight stdio MCP server that exposes typed tools backed by the engine API, while keeping no vision state itself.

It can be used to connect local image/video/RTSP streams to an agent workflow. The agent can request inference, manage streams, inspect tracked objects, configure polygon zones and crossing lines, query live or historical metrics, and retrieve event evidence.

Implementation details include lazy RF-DETR model loading with MPS/CPU resolution, threaded stream capture with bounded latest-frame queues and reconnect backoff, ByteTrack tracking, zone/line/dwell analytics, SQLite-backed metrics, and safeguards for path traversal, URL allowlisting, and RTSP credential redaction.

This contribution is additive: it is scoped entirely to vision-mcp/ and does not change existing RF-DETR code or dependencies.

Related Issue(s): None

Type of Change

  • New feature (non-breaking change that adds functionality)

Testing

  • I have added/updated tests for this change

Test details:

Added vision-mcp/tests/test_phase1_contract.py, covering the initial shared API-contract behavior. I have not yet completed a full local validation run, so I am leaving the local-testing checkbox unchecked.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable)

Additional Context

I am applying to Roboflow, where contributing to an open-source repository was part of the application process. I chose to make this contribution because I wanted to build something genuinely useful around RF-DETR rather than submit a minimal or cosmetic change.

The goal was to explore a practical way for AI agents to use RF-DETR in a local, privacy-conscious vision system while preserving a clean separation between agent-facing tools and the performance-sensitive inference path. I would appreciate feedback on whether this architecture fits the project’s direction, especially since this is a relatively substantial additive feature and was not tied to a pre-approved issue.

…ront end

Adds a standalone `vision-mcp` package giving an agent structured access to live RF-DETR vision: inference, tracking, zone and line analytics, metrics, and event evidence.

Two processes, one package:

- `vision-engine` — long-lived asyncio daemon owning cameras, model weights, inference workers, SQLite and artifacts. Local HTTP API bound to 127.0.0.1.
- `vision-mcp` — thin stdio MCP server holding no vision state; each tool is a typed wrapper over one engine endpoint.

The split keeps the agent out of the per-frame path and keeps historical metrics available when no MCP client is running. The engine contract is defined once as Pydantic models shared by both sides.

Includes lazy model loading with warm weight reuse and MPS/CPU device resolution; a stream pipeline with per-stream capture threads, bounded latest-frame queues and reconnect backoff; ByteTrack tracking with polygon zones, line crossings and dwell times; live and historical metrics aggregated into SQLite; and path-traversal plus URL allowlist checks with RTSP credentials redacted from logs.

Additive only — no existing rfdetr code is modified, and the new dependencies are scoped to vision-mcp/.
@socket-security

socket-security Bot commented Aug 3, 2026

Copy link
Copy Markdown

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86%. Comparing base (6674d85) to head (6400a64).

Additional details and impacted files
@@           Coverage Diff           @@
##           develop   #1271   +/-   ##
=======================================
- Coverage       86%     86%   -1%     
=======================================
  Files          114     114           
  Lines        14880   14880           
=======================================
- Hits         12835   12753   -82     
- Misses        2045    2127   +82     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f78fcc0b86

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vision-mcp/src/vision_mcp/engine/queries.py Outdated
Comment thread vision-mcp/src/vision_mcp/engine/queries.py Outdated
Comment thread vision-mcp/src/vision_mcp/inference/images.py Outdated
Comment thread vision-mcp/src/vision_mcp/analytics/observer.py Outdated
Comment thread vision-mcp/config.example.yaml Outdated
@@ -0,0 +1,10 @@
"""Local-first RF-DETR vision engine and MCP server.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the required license header to all Python files

This file, representative of the newly added Python package, begins directly with a docstring, and the other added Python modules do likewise; they must all include the repository's required RF-DETR Apache license header before their module content.

AGENTS.md reference: AGENTS.md:L144-L152

Useful? React with 👍 / 👎.

@Borda
Borda marked this pull request as draft August 3, 2026 09:29
@Borda

Borda commented Aug 3, 2026

Copy link
Copy Markdown
Member

I wanted to build something genuinely useful around RF-DETR rather than submit a minimal or cosmetic change.

Overall, before sending any PR, especially this large, it is better to open issues and discuss the scope and what would be the desired impact... Still, from the description, it is not clear what the benefit is for the RF-DETR package to have MCP, and what the MCO brings to the table for package users?

The goal was to explore a practical way for AI agents to use RF-DETR in a local, privacy-conscious vision system while preserving a clean separation between agent-facing tools and the performance-sensitive inference path.

While you say you want to keep it separate, why should it be added to this package?

I would appreciate feedback on whether this architecture fits the project’s direction, especially since this is a relatively substantial additive feature and was not tied to a pre-approved issue.

I think this would be a better fit - https://github.com/roboflow/computer-vision-skills

@ARDA7787

ARDA7787 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

It can offered as an extra package, core package can be kept separate. It provides a maintained, version-compatible adapter around RF-DETR for users using agentic vision workflows. "https://github.com/roboflow/computer-vision-skills" - this repo is for agent configuration managed by roboflow mcp, it does not contain the server implementation.

@ARDA7787

ARDA7787 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

I agree this is a product decision first, I should have opened an issue before proposing a change of this size.

@ARDA7787
ARDA7787 marked this pull request as ready for review August 5, 2026 04:06

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6a659453a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vision-mcp/src/vision_mcp/storage/schema.py Outdated
Comment on lines +90 to +91
if not path.exists() or not path.is_file():
raise VisionError(ErrorCode.INVALID_IMAGE, "File does not exist.", {"path": str(path)})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Continue checking later filesystem roots

When filesystem_roots has multiple entries and a caller provides a relative source or checkpoint such as image.jpg, resolve_within() succeeds for the first root, so this branch raises if root1/image.jpg is missing instead of trying root2/image.jpg; valid files in later configured roots become unreachable. Treat a missing file as a failed candidate and only raise after all roots have been checked.

Useful? React with 👍 / 👎.

@Borda
Borda marked this pull request as draft August 5, 2026 07:01
ARDA7787 and others added 7 commits August 5, 2026 10:47
docformatter can collapse blank lines around the docstrings it rewraps, so
ruff-format needs the last word on blank-line and whitespace formatting.
Running it first stops the two hooks from fighting on every pass.

Split docformatter into two hooks: vision-mcp/ wraps at 110 to match the
ruff line-length in vision-mcp/pyproject.toml, while the repo root keeps
its default width. Re-wraps docstrings across vision-mcp/src accordingly.
@ARDA7787
ARDA7787 marked this pull request as ready for review August 17, 2026 05:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8bdbff4688

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +21 to +25
"CREATE TABLE IF NOT EXISTS stream_health ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, stream_id TEXT NOT NULL, at REAL NOT NULL, "
"state TEXT NOT NULL, health TEXT NOT NULL, processed_fps REAL NOT NULL, "
"queue_depth INTEGER NOT NULL, dropped_frames INTEGER NOT NULL, last_error TEXT)",
"CREATE TABLE IF NOT EXISTS detection_summaries ("

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Create the processing metrics table

For any configured stream, the aggregator's first metrics flush inserts into processing_metrics, and the detection-rate/latency/throughput/drop-rate queries select from the same table, but the migrations never create it. On a fresh database those paths raise no such table: processing_metrics instead of returning historical metrics, so add the table with the columns written by MetricsCollector._metrics_row before these writes can run.

Useful? React with 👍 / 👎.

Comment on lines +130 to +131
detections = sv.Detections(xyxy=boxes, confidence=scores, class_id=class_id)
detections.data["keypoints"] = xy

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve keypoint class names when converting

When detect_keypoints handles RFDETRKeypointPreview, RF-DETR's sv.KeyPoints already carries resolved labels in keypoints.data["class_name"] because keypoint class_id values can be sparse or shifted; rebuilding a Detections with only class_id drops that data. convert() then falls back to positional class_names[class_id], so keypoint detections can be reported with the wrong class name or be filtered out by classes=["person"]; copy the keypoint data (at least class_name) into the converted detections.

Useful? React with 👍 / 👎.

@Borda
Borda marked this pull request as draft August 17, 2026 07:12
@ARDA7787
ARDA7787 marked this pull request as ready for review August 26, 2026 05:18
@ARDA7787
ARDA7787 marked this pull request as draft August 26, 2026 05:19

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8bdbff4688

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

def __init__(self, config: TrackingConfig, processing_fps: float) -> None:
self._tracker = sv.ByteTrack(
track_activation_threshold=config.track_activation_threshold,
lost_track_buffer=max(1, round(config.lost_track_seconds * processing_fps)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass ByteTrack's buffer in its 30-FPS units

At the default 3 FPS, this passes lost_track_buffer=3, but Supervision 0.29 scales that value by frame_rate / 30, yielding a zero-frame retention window instead of the configured one second. A detection missing for one processed frame is therefore assigned a new ID when it returns, inflating unique-object, zone-entry, and occupancy metrics; convert lost_track_seconds to ByteTrack's 30-FPS buffer units rather than multiplying it by the stream FPS.

Useful? React with 👍 / 👎.

Comment on lines +97 to +99
return StreamingResponse(
services.preview.mjpeg(runtime), media_type="multipart/x-mixed-replace; boundary=frame"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Advertise the boundary emitted by the MJPEG generator

When debug preview is enabled, this response advertises boundary=frame, while PreviewService.mjpeg() emits chunks beginning with --visionmcpframe. Multipart clients search for the advertised delimiter, so /debug/stream cannot render frames; use the same boundary constant for the response content type and generated parts.

Useful? React with 👍 / 👎.

Comment on lines +69 to +71
"SELECT class_name, SUM(detections) AS n FROM detection_summaries "
"WHERE stream_id = ? AND bucket_start >= ? AND bucket_start < ? GROUP BY class_name",
(stream_id, query.start, query.end),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include aggregates that overlap the count window

Whenever the rolling query start falls inside a stored aggregation bucket, this predicate excludes that entire bucket even though part of it belongs to the requested window. With the default 30-second aggregation cadence, get_counts_by_class can therefore omit almost 30 seconds of detections; select overlapping buckets and account for their overlap, as the detection-rate and throughput paths already do.

Useful? React with 👍 / 👎.

Comment on lines +42 to +44
@app.exception_handler(VisionError)
async def vision_error(request: Request, exc: VisionError) -> JSONResponse:
return JSONResponse(status_code=exc.http_status, content=exc.to_payload())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist handled failures for the recent-errors tool

When a tool request raises a VisionError, this handler only returns the error response; the repository has no callers of EventSink.record_error, which is the sole writer to the errors table. Consequently get_recent_errors remains empty even after model, database, or inference request failures, so the exception paths need to record the failure before responding.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants