Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Bailian (DashScope) API key — get from https://platform.qianwenai.com/home/api-keys
# REQUIRED. Treat as a secret; never commit this file.
DASHSCOPE_API_KEY=

# Model ID. qwen3.7-plus is multimodal (text+image+video) on Bailian.
QWEN_MODEL=qwen3.7-plus

# OpenAI-compatible endpoint (documented multimodal/video support).
# Anthropic-compatible /apps/anthropic does NOT support video input.
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1

# Per-request timeout in seconds (video processing can be slow).
QWEN_REQUEST_TIMEOUT=300
18 changes: 18 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Summary

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a top-level heading on the first line.

Markdownlint rule MD041 requires the first line to be an H1, so this template can fail the documented formatting gate.

Proposed fix
-## Summary
+# Pull Request Summary
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Summary
# Pull Request Summary
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 1-1: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/PULL_REQUEST_TEMPLATE.md at line 1, Update the first heading in the
pull request template from a level-two heading to a top-level H1 heading,
preserving the existing “Summary” text so the template satisfies Markdownlint
rule MD041.

Source: Linters/SAST tools


<!-- What does this change do and why? -->

## Checklist

- [ ] `npm run typecheck` passes
- [ ] `npm run lint` passes (`--max-warnings 0`)
- [ ] `npm run format:check` passes
- [ ] `npm test` passes
- [ ] No secrets, API keys, or `.env` files committed
- [ ] Did NOT use `git commit --no-verify` (see AGENTS.md)
- [ ] New tools/behavior covered by tests (unit + mocked e2e)
- [ ] Live behavior verified locally with `LIVE=1 npm run test:live` (if behavior changed)

## Notes for reviewers

<!-- Anything non-obvious, fragile assumptions, or follow-ups -->
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
push:
branches: [main, develop]
pull_request:
Comment on lines +3 to +6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

No concurrency group defined.

zizmor flags the workflow for missing a concurrency setting; overlapping pushes/PRs can run redundant CI jobs concurrently, wasting compute.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 3-6: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 3 - 6, Add a workflow-level
concurrency configuration near the existing on trigger in the CI workflow, using
a group key that distinguishes the relevant workflow and ref while canceling
in-progress runs when superseded. Keep the current push and pull_request
triggers unchanged.

Source: Linters/SAST tools


permissions:
contents: read

jobs:
quality:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
Comment on lines +19 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Actions unpinned and checkout doesn't disable credential persistence.

zizmor flags actions/checkout@v4, actions/setup-node@v4, and actions/upload-artifact@v4 as unpinned (mutable tag rather than a commit SHA), and checkout doesn't set persist-credentials: false, leaving the git credential available to later steps/artifacts unnecessarily.

Also applies to: 33-37

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 19-19: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 19-19: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 19 - 23, Pin the actions/checkout,
actions/setup-node, and actions/upload-artifact uses to immutable commit SHAs
while preserving their current versions and configuration. In the checkout step,
set persist-credentials to false so subsequent steps cannot reuse the checkout
token.

Source: Linters/SAST tools

- name: Install (skip husky in CI)
run: npm ci
env:
HUSKY: "0"
- run: npm run typecheck
- run: npm run lint
- run: npm run format:check
- run: npm test
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist-node-${{ matrix.node }}
path: dist
if-no-files-found: error
20 changes: 20 additions & 0 deletions .github/workflows/secrets-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Secret Scan

on:
push:
branches: [main, develop]
pull_request:
Comment on lines +3 to +6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

No concurrency group defined.

Same finding as ci.yml — no concurrency setting, so parallel pushes/PRs can run duplicate scans.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 3-6: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/secrets-scan.yml around lines 3 - 6, Add a concurrency
configuration to the workflow alongside the existing “on” triggers, using a
stable group key that distinguishes the relevant branch or pull request context
and cancels or prevents redundant in-progress secrets scans consistently with
ci.yml.

Source: Linters/SAST tools


permissions:
contents: read

jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
Comment on lines +15 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Actions unpinned and checkout doesn't disable credential persistence.

Same as flagged in ci.yml: actions/checkout@v4 and gitleaks/gitleaks-action@v2 are unpinned mutable tags, and checkout doesn't set persist-credentials: false.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 15-17: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/secrets-scan.yml around lines 15 - 18, Update the workflow
steps using actions/checkout and gitleaks/gitleaks-action to immutable commit
SHAs instead of mutable version tags, and set persist-credentials to false in
the checkout step while preserving fetch-depth: 0.

Source: Linters/SAST tools

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 changes: 30 additions & 0 deletions .github/workflows/smoke-live.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Live Smoke

on:
workflow_dispatch:
schedule:
# Weekly sanity check (Mon ~03:17 UTC, off the :00 mark).
- cron: "17 3 * * 1"
Comment on lines +3 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

No concurrency group defined.

A manual workflow_dispatch run could overlap with the weekly scheduled run, duplicating live API calls (and quota usage) since no concurrency group is set.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 3-7: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/smoke-live.yml around lines 3 - 7, Add a workflow-level
concurrency group to smoke-live.yml so workflow_dispatch and scheduled runs
share the same serialized execution group and cannot overlap. Configure the
group to use the workflow identity and cancel-in-progress behavior appropriate
for preventing duplicate live API calls, without changing the existing triggers.

Source: Linters/SAST tools


permissions:
contents: read

jobs:
live:
runs-on: ubuntu-latest
env:
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
LIVE: "1"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Comment on lines +19 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Actions unpinned and checkout doesn't disable credential persistence.

Same finding as the other two workflows: actions/checkout@v4 and actions/setup-node@v4 are unpinned, and checkout doesn't set persist-credentials: false.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 19-19: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 19-19: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/smoke-live.yml around lines 19 - 20, Update the workflow
steps using actions/checkout and actions/setup-node to pin both actions to
immutable commit SHAs, preserving their current major-version behavior. Add
persist-credentials: false to the actions/checkout step.

Source: Linters/SAST tools

with:
node-version: 20
cache: npm
- name: Install (skip husky in CI)
run: npm ci
env:
HUSKY: "0"
- run: npm run build
- name: Run live image understanding (bundled fixture)
run: npm run test:live
38 changes: 38 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
# === Secrets — NEVER commit ===
.env
.env.*
!.env.example

# === Node ===
node_modules/
dist/
build/
*.tsbuildinfo
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
.npm
.pnpm-store/

# === Test / coverage ===
coverage/
.nyc_output/
.vitest-cache/

# === Biome / tooling caches ===
.biome-cache/

# === IDE / editor ===
.vscode/
.idea/
*.swp
*.swo
*~

# === OS ===
.DS_Store
Thumbs.db

# === Reference / vendor ===
ref/
11 changes: 11 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env sh
# Pre-commit: secret guard + lint/format staged files + gitleaks (if installed).
# NEVER bypass with --no-verify (see AGENTS.md).
node scripts/check-secrets.mjs || exit 1
npx lint-staged || exit 1
if command -v gitleaks >/dev/null 2>&1; then
gitleaks protect --staged --redact || exit 1
else
echo "⚠ gitleaks not installed, skipping generic secret scan."
echo " Install: https://github.com/gitleaks/gitleaks"
fi
Comment on lines +6 to +11

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Make generic secret scanning fail closed.

When gitleaks is absent, the hook succeeds and the fallback only catches selected root .env files and sk-ws- keys. Other credentials, nested .env files, or .env.test can therefore be committed.

  • .husky/pre-commit#L6-L11: require a generic scanner to be installed/provisioned and fail the hook when it is unavailable.
  • scripts/check-secrets.mjs#L6-L43: broaden the fallback to inspect all staged .env* paths and relevant credential patterns.

Based on learnings, “Never commit secrets, API keys, tokens, or .env files.”

📍 Affects 2 files
  • .husky/pre-commit#L6-L11 (this comment)
  • scripts/check-secrets.mjs#L6-L43
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.husky/pre-commit around lines 6 - 11, Make secret scanning fail closed: in
.husky/pre-commit, require gitleaks to be installed or provisioned and exit
nonzero when unavailable instead of skipping. In scripts/check-secrets.mjs,
inspect every staged .env* path, including nested files and .env.test, and
expand credential detection beyond root .env files and sk-ws- keys to cover
relevant secret patterns.

Source: Learnings

5 changes: 5 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env sh
# Pre-push: type-check + full test suite (live tests auto-skip without LIVE=1).
# NEVER bypass with --no-verify (see AGENTS.md).
npm run typecheck || exit 1
npm test || exit 1
Comment on lines +4 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Run every required quality gate before pushing.

Add lint, format:check, and build; this hook currently permits failures in all three checks. Based on learnings, “Before push, all quality gates must pass: typecheck, lint, format check, tests, and build.”

Proposed fix
 npm run typecheck || exit 1
+npm run lint || exit 1
+npm run format:check || exit 1
 npm test || exit 1
+npm run build || exit 1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
npm run typecheck || exit 1
npm test || exit 1
npm run typecheck || exit 1
npm run lint || exit 1
npm run format:check || exit 1
npm test || exit 1
npm run build || exit 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.husky/pre-push around lines 4 - 5, Update the pre-push hook commands
alongside the existing typecheck and test gates to also run lint, format:check,
and build, ensuring each command exits nonzero on failure so every quality gate
must pass before pushing.

Source: Learnings

15 changes: 15 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Vendored / not part of this project's code
ref/
.agents/
.claude/
skills-lock.json

# Build & deps
dist/
coverage/
node_modules/
package-lock.json
*.lock

# Git hooks (shell, not prettier's domain)
.husky/
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"endOfLine": "lf"
}
68 changes: 68 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# AGENTS.md — Rules for AI agents working on this repo

Hard rules. Follow exactly. These exist to keep agents from shipping broken or leaky code.

## Secrets (highest priority)

- **Never commit secrets, API keys, tokens, or `.env` files.** Keys live only in `.env` (gitignored) or environment variables.
- **Never hardcode a key in source, tests, configs, or docs.** Read it from `DASHSCOPE_API_KEY` via `src/config.ts`.
- **Never paste a real key into a fixture.** Tests use dummy values (`sk-test`, `sk-secret-key-…`). The pre-commit `check-secrets.mjs` blocks `sk-ws-…` (real Bailian keys); don't try to evade it.
- If you accidentally stage a secret: unstage it, rotate the key immediately, and tell the maintainer.

## Git hooks — never bypass

- **Never use `git commit --no-verify` or `git push --no-verify`.** Hooks run secret scan, lint, format, type-check, and tests for a reason.
- If a hook fails, fix the cause. Do not work around it.
- After first clone: run `npm install` (the `prepare` script installs husky hooks). Verify with `git config core.hooksPath` → `.husky`.

## Quality gates — all must pass before push

Run these locally before considering work done:

```bash
npm run typecheck # tsc --noEmit, strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes
npm run lint # eslint, typescript-eslint strictTypeChecked, --max-warnings 0
npm run format:check
npm test # vitest, unit + mocked e2e (live tests auto-skip without LIVE=1)
npm run build # tsc -p tsconfig.build.json -> dist/
```

CI runs the same on Node 20 and 22. Local green ≠ CI green if you skip a step.

## Code standards

- **TypeScript strict.** No `any` in `src/` (allowed sparingly in `test/` for fixture typing). No `@ts-ignore`. No non-null assertions in `src/`.
- Prefer narrow types and `unknown` over `any` when parsing external JSON (see `src/bailian.ts`).
- The DashScope payload builder (`buildPayload`) is intentionally injectable — if the `video_url`/`image_url` content block shape changes, change it in one place.
- Do not add a new runtime, language, or heavy dependency without explicit maintainer approval.
- Match existing style; let `prettier` and `eslint --fix` handle formatting.

## Tool surface

The server exposes 8 MCP tools (see `src/server.ts`): `analyze_video`, `analyze_image`, `summarize_video`, `extract_video_text`, `video_qa`, `compare_video_frames`, `check_endpoint_status`, `list_capabilities`. Do not silently change a tool's name or argument schema — that breaks MCP clients. Add new tools rather than renaming.

`check_endpoint_status` must redact the API key (`redactKey`). There is a test asserting no key leaks — keep it passing.

## Backend

- Endpoint: Bailian (DashScope) OpenAI-compatible mode, `${DASHSCOPE_BASE_URL}/chat/completions` (default `https://dashscope.aliyuncs.com/compatible-mode/v1`).
- Model: `qwen3.7-plus` (multimodal, native video — **no client-side frame extraction**).
- The Anthropic-compatible `/apps/anthropic` endpoint does NOT support video input. Do not switch to it for multimodal tools.
- Video frame sampling is server-side (fixed 0.5s/frame on OpenAI-compatible mode). Do not add frame extraction logic.

## Testing

- Unit + mocked e2e use **msw** to mock `fetch` — no real API calls, no cost. Keep it that way.
- Live tests (`test/live.test.ts`) run only with `LIVE=1` and a real `DASHSCOPE_API_KEY`. They hit the real API and cost tokens. Run locally to verify behavior; never make them part of the default `npm test`.
- Every new tool or branch of logic gets a test. Coverage threshold is 85%.

## Filesystem

- Delete files with `trash`, never `rm` (per global policy).
- `ref/` is vendored reference material — read-only, do not modify, do not import from.

## Fragile assumptions (verify before relying on)

1. The OpenAI-compatible endpoint accepts a `video_url` content block for `qwen3.7-plus`. If a live call rejects it, the fallback is the native DashScope `video` content type or switching to `qwen-vl-max-latest`. Change `contentBlock()` in `src/bailian.ts`.
2. The exact model id string `qwen3.7-plus`. Verify against the Bailian model list if a call returns a model-not-found error.
3. Local video >10MB cannot be sent as a base64 data URL (body too large). Host large local videos at a public URL instead.
112 changes: 112 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# qwen-omni-mcp

An [MCP](https://modelcontextprotocol.io) server that gives Claude Code and other AI agents **video and image understanding** via [Bailian (DashScope)](https://platform.qianwenai.com) using the multimodal **Qwen3.7-Plus** model.

Qwen3.7-Plus reads video natively — **no client-side frame extraction**. You pass a public media URL; the model does the rest.

## Highlights

- **Native video understanding** — send a video URL, get grounded analysis
- **Image understanding** — describe, Q&A, OCR
- **Convenience tools** — summarize, text extraction, frame comparison, Q&A
- **npx-launchable** — one line in your MCP client config
- **Hardened** — secret-leak pre-commit guard + gitleaks, strict TypeScript, full CI

## Install

No global install needed. Run directly with npx:

```bash
npx -y qwen-omni-mcp
```

For local development:

```bash
git clone <this-repo>
cd qwen-omni-mcp
npm install # also installs husky git hooks
cp .env.example .env # fill in DASHSCOPE_API_KEY
npm run dev # run from source via tsx
```

## Configuration

All config is via environment variables (loaded from `.env` by `dotenv`):

| Variable | Required | Default | Description |
| ---------------------- | -------- | --------------------------------------------------- | ------------------------------ |
| `DASHSCOPE_API_KEY` | yes | — | Bailian API key |
| `QWEN_MODEL` | no | `qwen3.7-plus` | Model id (multimodal) |
| `DASHSCOPE_BASE_URL` | no | `https://dashscope.aliyuncs.com/compatible-mode/v1` | OpenAI-compatible endpoint |
| `QWEN_REQUEST_TIMEOUT` | no | `300` | Per-request timeout in seconds |

Get a key at <https://platform.qianwenai.com/home/api-keys>.

> The Anthropic-compatible `/apps/anthropic` endpoint does **not** support video input, so this server uses the OpenAI-compatible endpoint.

## Use with Claude Code

Add to your MCP client config:

```json
{
"mcpServers": {
"qwen-omni-mcp": {
"command": "npx",
"args": ["-y", "qwen-omni-mcp"],
"env": {
"DASHSCOPE_API_KEY": "your-key"
}
}
}
}
```

For local development without publishing:

```json
{
"mcpServers": {
"qwen-omni-mcp": {
"command": "npx",
"args": ["tsx", "src/index.ts"],
"env": { "DASHSCOPE_API_KEY": "your-key" }
}
}
}
```

## Tools

| Tool | Description |
| ----------------------- | ---------------------------------------------- |
| `analyze_video` | Analyze a video URL with a custom prompt |
| `analyze_image` | Analyze an image URL with a custom prompt |
| `summarize_video` | Brief / standard / detailed summary |
| `extract_video_text` | Extract on-screen text and transcribe speech |
| `video_qa` | Ask a specific question about a video |
| `compare_video_frames` | Analyze changes and progression across a video |
| `check_endpoint_status` | Show configured endpoint/model (key redacted) |
| `list_capabilities` | List server capabilities and supported formats |

Media must be reachable via a public `http`/`https` URL. Large local videos should be hosted at a public URL (base64 data URLs over ~10MB will be rejected).

## Development

```bash
npm run typecheck # strict tsc
npm run lint # eslint, --max-warnings 0
npm run format:check # prettier
npm test # unit + mocked e2e (no API cost)
npm run build # emit dist/
LIVE=1 npm run test:live # real API calls (costs tokens)
```

CI (`.github/workflows/ci.yml`) runs the same gates on Node 20/22. `secrets-scan.yml` runs gitleaks. `smoke-live.yml` (manual / weekly) runs one real image call.

See [AGENTS.md](AGENTS.md) for the full set of agent rules (never bypass hooks, never commit secrets, etc.).

## License

MIT
Loading
Loading