Contributing

Thanks for your interest in improving NovelAI Image MCP! This page covers the practical workflow. For the project’s code of conduct, see CODE_OF_CONDUCT.md.

Before you start

  1. Open an issue describing your proposed change. This is especially important for new features or API changes — we’ll discuss scope and design before you spend time on code.

  2. Check the issue tracker for related work in progress.

Development setup

git clone https://github.com/xinvxueyuan/NovelAI-Image-MCP.git
cd NovelAI-Image-MCP

# Sync the workspace virtualenv (server + docs + dev tools)
uv sync

# (Optional) Install Node-side tooling for husky + markdownlint + turbo
corepack enable pnpm
pnpm install --frozen-lockfile

The first uv sync downloads all transitive dependencies; subsequent runs are instant.

Workflow

# 1. Branch off main
git checkout -b feat/my-feature

# 2. Implement your change.
#    - Source under apps/server/src/
#    - Tests under apps/server/tests/
#    - Docs under apps/docs/source/ (if user-facing)

# 3. Run all checks locally before pushing.
uv run --directory apps/server poe check
# Equivalent to: ruff format-check + ruff check + pyright + pytest

# 4. (If you touched docs) verify the build:
uv run --package novelai-image-mcp-docs sphinx-build -b html apps/docs/source apps/docs/_build/html -W --keep-going

# 5. (If you touched markdown) verify markdownlint:
pnpm exec markdownlint-cli2

# 6. (If you touched licensing) verify REUSE compliance:
uv run reuse lint

# 7. Commit using Conventional Commits + gitmoji:
git commit -m "✨ feat: add support for V5 model ids"

# 8. Push and open a PR:
git push -u origin feat/my-feature
gh pr create --title "..." --body "..."

Pre-commit hook

If you’ve installed pnpm dependencies, a .husky/pre-commit hook runs automatically on every git commit:

  1. prek — ruff --fix, ruff-format, and general hooks (trailing-whitespace, end-of-file-fixer, etc.).

  2. markdownlint — only when .md files are staged.

  3. pyright — only when .py files are staged.

  4. pytest — only when .py files are staged (slowest phase).

If prek modifies files, the hook re-stages them and retries once. If any phase fails, the commit aborts with a clear message.

To bypass for a WIP commit (use sparingly):

git commit --no-verify -m "wip: ..."

Warning

CI runs the same checks as the hook. A --no-verify commit will still fail CI — bypass only for genuinely WIP work that you’ll rebase before opening a PR.

Coding standards

Python

  • Style: enforced by ruff (preview mode). See apps/server/pyproject.toml for the rule set. Run uv run --directory apps/server ruff format to auto-format.

  • Types: enforced by pyright (standard mode). Type-check must pass.

  • Docstrings: Google style (the project’s pydocstyle convention). Required on public APIs; optional on private helpers.

  • Imports: from __future__ import annotations is required at the top of every module (PEP 563 + Ruff TC rules).

  • Async: tools are async. Use httpx.AsyncClient for HTTP, pytest-asyncio for tests (asyncio_mode=auto).

Markdown

  • Use MyST Markdown (the docs site’s parser).

  • Line length is unlimited (MD013 disabled).

  • Headings may not end with .,;:! (MD026).

  • Inline HTML is allowed (MD033 disabled) for callouts / details.

  • Run pnpm exec markdownlint-cli2 --fix to auto-fix most issues.

Licensing

  • All files must carry SPDX license headers. REUSE.toml declares them by glob — usually you don’t need to add anything manually.

  • Run uv run reuse lint to verify compliance.

  • Software code: MIT. Documentation: MIT. Visual elements + infrastructure: CC0-1.0.

Test coverage

  • Coverage floor: 70% (enforced by --cov-fail-under=70).

  • Tests live in apps/server/tests/ and use pytest-asyncio + respx (HTTP mocking).

  • Every new public function should have at least one test.

  • Bugs should include a regression test.

See Testing for the test layout and fixture strategy.

Documentation

User-facing changes (new tool, new parameter, behavior change) must update the docs:

  • Tools → apps/docs/source/tools/<tool>.md

  • Tutorials → apps/docs/source/tutorials/<tutorial>.md

  • Configuration → apps/docs/source/configuration.md

  • API → automatically generated by sphinx.ext.autodoc from docstrings

To preview docs locally:

uv run --package novelai-image-mcp-docs sphinx-autobuild apps/docs/source apps/docs/_build/html --open-browser

Contributing skills

Agent skills live under skills/<name>/SKILL.md and are registered in skills.sh.json at the repository root. To add a new skill:

  1. Run npx skills init <name> to scaffold the SKILL.md frontmatter (name + description only, no metadata block) and the ## When to use## Instructions body structure.

  2. Fill in the content — distribute guidance on demand, don’t duplicate what other skills already cover.

  3. Add the skill to skills.sh.json under the appropriate grouping (CLI / MCP Tools / Workflows).

  4. Update the Agent skills docs page and the README skills table if the skill is user-facing.

See AGENTS.md → “Agent Skills (skills.sh)” for the full scaffold conventions (frontmatter rules, H1 = skill name, description < 200 chars, quoting rules for YAML special characters).

Translating docs

The docs site supports multiple languages via per-language source trees. English lives at the apps/docs/source/ root; each additional language lives under apps/docs/source/<lang-code>/ (e.g., source/zh/, source/ja/). All languages share the same conf.py.

Rules

  1. Full translations only — no stubs. A translated page must cover the full English source. Code blocks remain unchanged; only prose is translated. Do NOT create “translation in progress” placeholder pages — they break the build’s -W warning gate and confuse users.

  2. Toctree scope = translated pages only. The index.md toctree in each language directory must only reference pages that exist under that language’s directory. If a section (e.g., tools/, tutorials/) is not translated, omit it from the translated toctree — users switch to English for untranslated sections.

  3. Preserve MyST directives. Keep all MyST syntax (:::tip, === "Unix" tabs, {toctree}, {image}, etc.) intact. Only translate the prose inside them.

  4. CJK punctuation in headings. MD026 forbids trailing .,;:! in headings — use 。、!? instead. (MD013 line-length is disabled, so long CJK lines are fine.)

Adding a new language

  1. Create apps/docs/source/<code>/ with at minimum index.md.

  2. Add the language to AVAILABLE_LANGUAGES in apps/docs/source/conf.py (tuple of (code, label, base_path)).

  3. Add the language to the matrix in .github/workflows/docs.yml.

  4. Build locally and confirm zero warnings:

    uv run --package novelai-image-mcp-docs sphinx-build -b html \
      apps/docs/source/<code> apps/docs/_build/<code>/html \
      -c apps/docs/source -D language=<code> -W --keep-going
    

Keeping translations in sync

When English source changes, update the translated page in the same PR if practical. Translated pages that fall badly out of sync should be deleted rather than left stale — a missing translation is better than a misleading one.

Commit messages

Follow Conventional Commits + gitmoji:

<emoji> <type>: <subject>

<body>

Type

Use for

feat

New feature

fix

Bug fix

docs

Documentation only

style

Formatting, whitespace, no code change

refactor

Code restructure, no behavior change

perf

Performance improvement

test

Adding / fixing tests

build

Build system, dependencies, CI

ci

CI configuration changes

chore

Misc maintenance

Common emojis:

Emoji

Type

feat

🐛

fix

📝

docs

🎨

style

♻️

refactor

⚡️

perf

test

🏗️

build

🔧

chore

Pull request

  • One PR per logical change.

  • Title: Conventional Commit format (feat:, fix:, etc.).

  • Description: link the issue, summarize the change, list any breaking changes.

  • All CI checks must pass before merge.

  • Squash-and-merge into main (the default branch).

Reporting bugs

Open a bug report issue. Include:

  • The exact command you ran.

  • The full error output (stack trace if Python).

  • The server version (via uv run python -c "import novelai_image_mcp; print(novelai_image_mcp.__version__)").

  • Your OS, Python version, and uv version.

  • A minimal reproduction (script or steps).

Reporting security issues

Do not open a public issue for security vulnerabilities. See SECURITY.md for the private disclosure process.

See also