Skip to content

feat: add snippet CRUD tools - #730

Open
zereight wants to merge 11 commits into
mainfrom
feat/snippets
Open

feat: add snippet CRUD tools#730
zereight wants to merge 11 commits into
mainfrom
feat/snippets

Conversation

@zereight

@zereight zereight commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Supersedes #470, which went stale. This rebases the original work onto current main, addresses the last review round, and aligns with current repo conventions.

Thanks to @renanliberato for the original implementation and the thorough review follow-ups in #470 — the tool design, schemas, and test matrix there are carried over here.

Tools (new opt-in snippets toolset)

  • list_snippets, get_snippet, create_snippet, update_snippet, delete_snippet
  • project_id selects project snippets; omitted means personal snippets (or the configured project when GITLAB_PROJECT_ID / GITLAB_ALLOWED_PROJECT_IDS is set)

On top of #470

  • get_snippet: single-file ref is honored — path resolves from files[0].path or snippet.file_name instead of always hitting /raw (last item from the feat: add snippet CRUD tools #470 review)
  • delete_snippet registered in deleteTools so modify mode blocks it; skill destructive list synced
  • Regenerated docs/tools (snippets.md), README tool list + discover_tools categories, skills/gitlab-mcp counts
  • delete_snippet added to permission-mode delete samples; findMockServerPort() signature updated

Verification

  • npx tsc --noEmit clean
  • npm run check:skill-sync passes; docs/tools drift-free
  • test/test-snippets.ts: 39 pass (incl. new file_name fallback regression test — verified it fails without the fix)
  • test/test-toolset-filtering.ts: 36 pass; test/test-permission-mode.ts: 13 pass; test:schema passes

renanliberato and others added 10 commits September 6, 2026 21:27
Adds list_snippets, get_snippet, create_snippet, update_snippet, and
delete_snippet — supporting both project-scoped and personal snippets
via optional project_id. Registered under a new non-default "snippets"
toolset.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The multi-file content endpoint requires a ref to construct the API URL.
Using default_branch caused 404s on instances where the default branch
is not "main"; fetching raw_url directly also 404s because it is a web
URL, not an API endpoint.

GitLab embeds the correct ref inside each file's raw_url
(.../raw/{ref}/{path}). Parse it out and pass it to the REST API
endpoint (/snippets/{id}/files/{ref}/{path}/raw) instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getSnippetFileRawContent now takes an explicit ref string instead of a
raw_url to parse. The ref-extraction logic is factored into a standalone
extractSnippetRef helper, resolved once at the call site rather than
once per file inside the fetch function.

get_snippet also gains an optional ref parameter (matching get_file_contents)
so callers can override the ref directly without relying on raw_url parsing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nested multi-file paths now encode the full file_path as a single segment
  (encodeURIComponent) per GitLab's /files/:ref/:file_path/raw contract.
  Previously, paths like dir/policy.md kept the slash and 404'd.
- getSnippetsEndpoint decodes URL-encoded project paths before re-encoding,
  matching the existing pattern in merge_requests/issues. Eliminates
  double-encoded URLs like /projects/group%252Fproject/snippets.
- GitLabSnippetSchema.visibility is now optional — the project snippets API
  does not consistently return it and ZodError was breaking list/get/create/
  update for valid responses.
- update_snippet exposes the modern multi-file contract: files[] with
  action (create/update/delete/move) and previous_path. The file_name+content
  shortcut remains for backward compatibility and is mutually exclusive.
SnippetFileUpdateActionSchema had previous_path optional even though the
schema description said it was required for action: "move". That left
malformed move actions to fail late with a GitLab 400 instead of being
caught at parse time. Adds a superRefine that surfaces the constraint as
a Zod error so LLM-generated update requests get a clear, local rejection.
get_snippet(include_content=true) previously threw if files[0].raw_url
was missing, even when the caller passed ref explicitly. Flip the guard
so raw_url is only required as a fallback for inferring ref.
getSnippetsEndpoint used to fall back to the global /snippets endpoint
whenever project_id was omitted, bypassing GITLAB_PROJECT_ID and
GITLAB_ALLOWED_PROJECT_IDS access boundaries. Route omitted project_id
through getEffectiveProjectId when either scope env is set, reusing its
existing default-when-unambiguous, reject-when-ambiguous semantics.
Personal snippets remain available only when no scope is configured.
SnippetFileUpdateActionSchema only enforced previous_path for 'move',
so malformed actions (create/update without content, delete without
file_path) and empty files[] arrays passed local validation and only
failed later as GitLab 400s. Expand the superRefine with the full
action matrix and require files[] to be non-empty when provided.
When include_content is true and the snippet has one file, use
getSnippetFileRawContent with the explicit ref instead of always
fetching HEAD via /raw. Rebase onto main and add regression test.

Co-authored-by: Cursor <cursoragent@cursor.com>
- register delete_snippet in deleteTools (modify mode) + destructive list sync
- add snippets GROUP_META/GROUP_ORDER and regenerate docs/tools (+snippets.md)
- list snippet tools in README + discover_tools categories
- sync skills/gitlab-mcp (counts, toolset table, destructive list)
- add delete_snippet to permission-mode delete samples
- get_snippet: resolve single-file path from files[0].path or file_name
  when ref is set; document project scope env fallback in schemas
- test: file_name fallback regression test; findMockServerPort() signature
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 20 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: bfbc38d1-d22a-45e8-a39f-22f9fe47d218

📥 Commits

Reviewing files that changed from the base of the PR and between a7d19ad and 446b7ea.

📒 Files selected for processing (14)
  • README.ko.md
  • README.md
  • README.zh-CN.md
  • docs/tools/index.md
  • docs/tools/meta.md
  • docs/tools/snippets.md
  • index.ts
  • schemas.ts
  • scripts/generate-tool-docs.ts
  • skills/gitlab-mcp/SKILL.md
  • test/test-permission-mode.ts
  • test/test-snippets.ts
  • test/test-toolset-filtering.ts
  • tools/registry.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@zereight

zereight commented Sep 6, 2026

Copy link
Copy Markdown
Owner Author

@cursor please review this PR.

@cursor

cursor Bot commented Sep 6, 2026

Copy link
Copy Markdown

PR 리뷰: feat: add snippet CRUD tools

판정: Approve (LGTM) — 머지해도 될 수준. 아래 nit 몇 개만 참고하면 됨.

직접 npm run build, snippet 관련 테스트 88개, check:skill-sync, test:schema 돌려봤고 전부 통과함.


잘한 거

1. 레포 컨벤션 잘 따름

releases/tags 패턴 그대로 따라감. tools/registry.ts opt-in snippets toolset, readOnlyTools / destructiveTools / deleteTools 등록, docs/skill/README 동기화까지 깔끔함.

2. get_snippet 콘텐츠 fetch 로직이 탄탄함

싱글/멀티 분기, ref 명시, file_name fallback, extractSnippetRef anchor 방식까지 #470 리뷰 포인트 잘 반영됐음.

        if (args.include_content) {
          const files = snippet.files ?? [];
          if (files.length > 1) {
            // ... multi-file: per-file raw fetch
          } else {
            const file = files[0];
            const filePath = file?.path ?? snippet.file_name ?? undefined;
            if (args.ref !== undefined && filePath) {
              result.content = await getSnippetFileRawContent(/* ... */);
            } else {
              result.content = await getSnippetRawContent(/* ... */);
            }
          }
        }

3. Zod 스키마가 실전형

files[] vs file_name+content mutual exclusion, SnippetFileUpdateActionSchema.superRefine으로 action별 필수 필드 parse-time 검증 — LLM이 이상한 payload 던져도 서버에서 터지기 전에 막음.

4. 프로젝트 스코프 강제가 맞음

GITLAB_PROJECT_ID / GITLAB_ALLOWED_PROJECT_IDS 켜져 있으면 personal endpoint 안 가고 project로 라우팅. allowlist 환경에서 personal snippet 노출 막는 거 의도 맞음.

5. 테스트가 과하지도 부족하지도 않음

39개 snippet TC + master/slash ref/nested path/no raw_url/double-encode 등 엣지 커버. file_name fallback regression test도 “/raw 안 타면 실패”로 검증해서 의미 있음.


사소한 nit (블로커 아님)

# 내용 심각도
1 createSnippetfile_name as string, content as string — Zod refine 뒤라 런타임은 안전한데, 타입 단언 대신 narrowing 쓰면 더 깔끔 nit
2 list_snippets는 pagination만 노출. GitLab API의 created_after 등 필터는 없음 — MVP로 OK, 나중에 추가 후보 enhancement
3 .well-known/mcp/server-card.json에 snippet tool 없음. scripts/generate-server-card.ts 있는데 이번 PR diff엔 없음. 의도적이면 OK, 아니면 regenerate 한번 nit
4 extractSnippetReffiles[].pathraw_url decoded path가 안 맞으면 실패함. GitLab이 path를 encoded로 주는 케이스는 아직 테스트 없음 — 실제 이슈 나오면 그때 edge case

보안 체크

  • SSRF: API base + 고정 path만 씀. OK
  • 권한 모드: delete_snippetdeleteTools + destructiveTools + skill sync. OK
  • 프로젝트 격리: getEffectiveProjectId 경유. OK
  • 입력 검증: Zod + per-action refine. OK

OWASP 쪽에서 눈에 띄는 취약점은 없음.


#470 대비 확인한 것

항목 상태
single-file ref/raw 대신 file path API
delete_snippet modify mode 차단
docs/skill/README 동기화
file_name fallback

총평

#470 리베이스 + 리뷰 반영이 제대로 됐고, 테스트/문서/권한 통합까지 맞춰져 있음. snippet CRUD를 opt-in toolset으로 넣는 방향도 기존 releases/tags와 일관됨.

블로킹 이슈 없음. Approve 추천.


진짜야? 제대로 한 거 맞아? — 코드 읽고 테스트 88+26 돌려본 결과 기준으로 말하는 거고, 실 GitLab 인스턴스 live test는 안 함.

자신감: 8.5/10 (live API 미검증, server-card 동기화 여부만 확신 못 함)

Open in Web Open in Cursor 

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