Guides

Agents

Give Claude, Cursor, Copilot and other coding agents jscpd through the MCP server, five installable skills and the ai reporter.

A coding assistant meets jscpd in three places. As an MCP server, jscpd runs inside the client (Claude Desktop, Claude Code, Cursor, Copilot and others) and answers questions about the project it scanned: whether a snippet already exists, which clones a file takes part in. As a set of skills, it gives the agent tested workflows that run the command line, from removing clones to porting a library. And whatever starts jscpd, the ai reporter prints the result in the fewest tokens.

The MCP server is built into jscpd since 5.0.16 and needs jscpd on the PATH of the machine the client runs on (Installation). The skills install with npx skills add and run jscpd through npx jscpd, so they need Node.js; compare-codebases and code-migration came with jscpd 5.4.0. The ai reporter has been part of jscpd since 4.1.0.
You wantUse
The assistant checks code against the project while it writes, inside the editorthe MCP server
The assistant runs a whole task, such as removing the clones or porting a librarya skill
A script or a prompt reads a jscpd report--reporters ai

The MCP server

Connect a client

jscpd speaks the Model Context Protocol over stdio. The client starts the process and talks to it on stdin and stdout, so there is no port and nothing to install besides jscpd. Claude Desktop, Claude Code and Cursor read the same shape of configuration:

.mcp.json
{
  "mcpServers": {
    "jscpd": {
      "command": "jscpd",
      "args": ["--mcp", "/path/to/project"]
    }
  }
}
ClientWhere the configuration lives
Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows
Claude Code.mcp.json in the project, or one command: claude mcp add --scope project jscpd -- jscpd --mcp /path/to/project
Cursor.cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project
VS Code with Copilot.vscode/mcp.json, with the shape below
.vscode/mcp.json
{
  "servers": {
    "jscpd": {
      "type": "stdio",
      "command": "jscpd",
      "args": ["--mcp", "/path/to/project"]
    }
  }
}

Give the project as an absolute path: the MCP documentation asks for absolute paths in Claude Desktop's configuration, and an absolute path works in every client. The detection options of a normal run apply to the scan and to every snippet check, so pass the ones your CI uses and the answers match its reports:

Terminal
jscpd --mcp --min-tokens 30 --format javascript,typescript /path/to/project

jscpd scans the paths once at start, reports the scan on stderr and then waits for the client. stdout carries protocol messages only.

jscpd MCP server (stdio): scanned 12 files, 6 clones in 20ms — waiting for client

Try it from a shell

The server reads one JSON-RPC message per line, the way a client sends them. On fixtures/type3-demo of the jscpd repository, this asks for the clones of one file:

Terminal
cd fixtures/type3-demo
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"shell","version":"0"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_file_clones","arguments":{"path":"inserted-line/save-user.js"}}}' | jscpd --mcp . 2>/dev/null

The first line of the answer is the initialize result, with "protocolVersion": "2025-06-18" and serverInfo (jscpd, version 5.4.0). The second is the tool result, whose text content is this JSON:

{
  "clones": 2,
  "duplications": [
    {
      "endA": 12,
      "endB": 11,
      "fileA": "inserted-line/save-account.js",
      "fileB": "inserted-line/save-user.js",
      "format": "javascript",
      "lines": 6,
      "startA": 6,
      "startB": 5,
      "tokens": 100
    },
    {
      "endA": 6,
      "endB": 6,
      "fileA": "inserted-line/save-account.js",
      "fileB": "inserted-line/save-user.js",
      "format": "javascript",
      "lines": 5,
      "startA": 1,
      "startB": 1,
      "tokens": 60
    }
  ],
  "file": "inserted-line/save-user.js",
  "returned": 2
}

Reading the answers

Every tool answers with compact JSON in one text content item. Clone lists come biggest first and are cut at limit entries, 100 unless the call says otherwise; the count next to a list (clones, count) is always the whole number, and a cut list carries a note that says so. Paths are relative to the scan root, in the form the other tools accept. When a call cannot use its arguments, such as a missing code or an unknown format, the server answers with a tool error (isError: true) and a message that says what to fix:

{"error": "unknown format 'klingon': run `cpd --list` for supported formats"}

An unknown tool name gets a JSON-RPC error. All four tools are read-only and never change the project's files. The initialize result also carries instructions, a short text that tells the assistant when to call which tool.

Check a snippet before writing it

check_duplication compares a snippet with the scanned project, so the assistant can reuse the existing copy. With the body of inserted-line/save-account.js as code and "format": "js", the demo answers:

{
  "count": 1,
  "duplications": [
    {
      "file": "inserted-line/save-account.js",
      "fileEndLine": 12,
      "fileStartLine": 1,
      "snippetEndLine": 12,
      "snippetStartLine": 1,
      "tokens": 172
    }
  ],
  "format": "javascript",
  "returned": 1
}

A snippet shorter than --min-tokens cannot match and says why instead of answering with an empty list. For let x = 1;:

{
  "count": 0,
  "duplications": [],
  "format": "javascript",
  "note": "snippet has 5 tokens, below the detection threshold of 50 (--min-tokens)"
}

With "similarity": 0.85 the answer also lists project functions whose syntax-tree shape resembles each function of the snippet, under similar with a similarCount; each entry has the file, its line range, the project function's name, the snippetName and a similarity ratio. This part works for JavaScript and TypeScript only, and 1 turns it off.

See a file's clones before refactoring it

get_file_clones lists every clone of one file, as the shell example above shows. The file you ask about can be fileA or fileB of a clone. A path that was not part of the scan answers with clones: 0.

Scan again after the assistant edits files

The other tools answer from the scan made at start. check_current_directory scans the configured paths again, replaces that scan and returns the new counts and clone list. With "limit": 2 on the demo:

{
  "clones": 6,
  "duplicatedLines": 42,
  "duplications": [
    {
      "endA": 15,
      "endB": 14,
      "fileA": "long-line/theme-branded.js",
      "fileB": "long-line/theme-default.js",
      "format": "javascript",
      "lines": 7,
      "startA": 8,
      "startB": 7,
      "tokens": 117
    },
    {
      "endA": 12,
      "endB": 11,
      "fileA": "inserted-line/save-account.js",
      "fileB": "inserted-line/save-user.js",
      "format": "javascript",
      "lines": 6,
      "startA": 6,
      "startB": 5,
      "tokens": 100
    }
  ],
  "files": 12,
  "note": "clone list truncated to 2; pass a higher 'limit' for more",
  "percentage": 9.051724137931034,
  "returned": 2
}

get_statistics returns the totals of the last scan and the same numbers per format, so the assistant can compare a project before and after a refactoring. Its statistics.total on the demo:

{
  "clones": 6,
  "duplicatedLines": 42,
  "duplicatedTokens": 528,
  "lines": 464,
  "newClones": 0,
  "newDuplicatedLines": 0,
  "percentage": 9.051724137931034,
  "percentageTokens": 11.865168539325843,
  "sources": 12,
  "tokens": 4450
}

Tools

ToolArgumentsAnswer
check_duplicationcode (required), format (required; a format name such as javascript or an extension such as js), limit, similarity (a ratio in (0, 1])format, count, returned, duplications[] with file, fileStartLine, fileEndLine, snippetStartLine, snippetEndLine, tokens; with similarity below 1 also similar[] and similarCount
get_file_clonespath (required; relative to the scan root or absolute), limitfile, clones, returned, duplications[] with format, fileA, startA, endA, fileB, startB, endB, lines, tokens
get_statisticsnonefiles, clones, statistics with total and one object per format: sources, lines, tokens, clones, duplicatedLines, duplicatedTokens, percentage, percentageTokens
check_current_directorylimitfiles, clones, duplicatedLines, percentage, returned, duplications[] in the shape of get_file_clones

The server answers initialize with protocol revision 2025-06-18 and also accepts clients of 2025-03-26 and 2024-11-05; a request for any other revision gets 2025-06-18.

The skills

A skill is a folder with a SKILL.md that an assistant reads when a task matches its description, so the agent runs jscpd with the right options and follows a tested workflow. jscpd ships five of them for skills.sh; the sources are in skills/ of the repository.

Terminal
npx skills add kucherenko/jscpd
npx skills add kucherenko/jscpd --skill dry-refactoring

The first line installs all five, the second one of them. Every skill runs jscpd through npx jscpd, so the agent needs no global install. Once a skill is installed, a request in its words is enough: "find and fix code duplication" runs the first two, "clean up this codebase" the third, "port this library to Rust" the last two.

SkillWhat it doesWhen the agent needs it
jscpdThe reference for jscpd itself: the flags, the passes for each kind of clone, the ai reporter's format, the config fileAny run of jscpd
dry-refactoringRemoves the clones jscpd found: reads each pair, triages it, picks a strategy, applies it, checks the clone is gone"Find and fix code duplication"
codebase-refactoringA pass over duplication, dead code and complexity, measured with --health before and after"Clean up this codebase", "pay down tech debt"
compare-codebasesCompares two folders function by function with --compare and explains the result (5.4.0+)"How far is the port?", "What does the Android app have that iOS lacks?"
code-migrationPorts a codebase to another language or framework, tests first, with --compare as the progress measure (5.4.0+)"Port this Python library to TypeScript"

jscpd

The reference the other skills build on. It tells the agent to scan with the ai reporter, to scope a run with --ignore and --format, and how to read each line of the report. It describes the default scan, which reports exact copies, and two noisier passes to run after those are dealt with: --ignore-identifiers --min-tokens 70 for copies that differ only in names and values, and --max-gap-lines 1 --similarity 0.85 for copies with a few edited lines or the same function structure. It also covers --summary for a hotspot ranking, --complexity for the complexity ranking alone, and the keys of .jscpd.json.

dry-refactoring

The workflow for removing clones. The agent runs jscpd with --reporters ai, reads both fragments of each clone, works out what the code does, and triages renamed and similar clones before touching them: a pair whose two sides do different things, intentional boilerplate, generated code, a pair that would need a vague name, or a pair under about ten lines is skipped and reported as skipped. The kind of clone picks the strategy, and the agent runs jscpd again with the same flags to confirm the clone is gone, highest impact first.

StrategyWhen
Extract functionThe duplicate is a block of logic
Extract module or utilityThe duplicate spans files in different domains
Extract constant or configThe duplicate is repeated data or configuration
Template or base classThe duplicate is a repeated class shape
ParameterizeA (renamed) clone: the same algorithm over different names or values
Unify near-miss copiesA [gap] clone: extract the common body and pass the divergence in
Merge similar functionsAn [ast] clone: extract the shared skeleton and inject what differs

Two of its tips save time on real projects. A clone between a .js and a .ts file, found with --cross-formats, usually means code was ported without deleting the original, so the fix is one implementation. Many (renamed) clones in one file usually point at one missing abstraction, and many across test files usually mean nothing, since test cases are supposed to look alike.

codebase-refactoring

Starts from one number and works where it is lowest:

Terminal
npx jscpd --health --reporters ai ./src
health 74 B (duplication 74, dead-code 72, complexity 76; 93 code lines)

The health score is a weighted mix of three sub-scores, and the lowest one names where the codebase hurts most. The skill then makes three passes in the order duplication, dead code, complexity: dry-refactoring for the clones, --dead-code for unused files, exports and imports (removed, or kept with a reason), and --complexity for the largest and most complex files, which get split or simplified. The order matters because an extraction moves call sites the dead-code pass would otherwise trace, and removed dead code changes which files rank as complex. After each pass the agent runs --health again and reports the change.

compare-codebases

jscpd --compare A B pairs every function of folder A with the function of folder B that does the same job, in one language or across two, and lists the functions that have no counterpart. The skill explains how the pairing works, by code with the embedding model and then by name, and what the high, medium and low levels mean. Its six steps: point at the code and keep vendored and generated files out with .gitignore or --ignore; ask before the one-time npx jscpd --semantic-download (548 MB); read the overview; check a sample of pairs and every low one; search the other folder for an unmatched function before calling it missing; and report the two percentages, the renamed pairs and the real gaps, with the HTML migration map for a reader who wants to explore.

Terminal
npx jscpd --compare python/ typescript/
npx jscpd --compare ios/ android/

The first line measures a port, source first and target second; the second checks two implementations of one app for parity. Comparing two codebases describes the reports.

code-migration

The port workflow, with --compare as the progress measure and the source's tests as the definition of done. The agent first builds a map from each source function to the tests that cover it, from a per-test coverage report, so a function is ported together with the tests that prove it. Then it ports the tests, keeping inputs and expected values, and keeps them out of the build until their functions land; it writes no stubs, because a stub under the source's name can pair by name and count as ported. Then it ports the code one function at a time, starting from the report's readyToPort list (unmatched functions whose callees already have a counterpart), turns on each function's tests as it lands, checks with coverage that they reach the new code, and reruns the report after each batch. Progress is reported as two numbers, tests ported and passing, and functions ported, with the functions left out on purpose and why.

The skill was tested by porting fs-extra 11.4.1 to a Rust addon. The port is on npm as @jscpd/fs-extra, with the code at kucherenko/fs-extra-rs; a run of the same task without the skill is kept at kucherenko/fs-extra-rs-plain for comparison.

The ai reporter

The ai reporter prints one line per clone and a summary, with no code fragments and no colors. On fixtures/type3-demo:

Terminal
jscpd . --reporters ai
Clones:
inserted-line/ save-account.js:1-6 ~ save-user.js:1-6
inserted-line/ save-account.js:6-12 ~ save-user.js:5-11
long-line/ theme-branded.js:1-8 ~ theme-default.js:1-8
long-line/ theme-branded.js:8-15 ~ theme-default.js:7-14
wide-gap/ place-order-guarded.js:1-6 ~ place-order.js:1-6
wide-gap/ place-order-guarded.js:9-15 ~ place-order.js:6-12
---
6 clones · 9.1% duplication
time: 5.439ms

On this demo the console reporter prints 2,099 characters and the ai reporter 394. On the repository's fixture corpus of 212 clones, the benchmark measured about 2,800 tokens for the ai reporter against about 23,000 for the console reporter, and compares it with other detectors.

Reading a clone line

The two fragments stand on either side of ~. A directory the two files share is written once, followed by a space, so inserted-line/ save-account.js:1-6 ~ save-user.js:1-6 names inserted-line/save-account.js and inserted-line/save-user.js. A clone within one file gives the path once and two ranges: file.js 10-25 ~ 45-60. No suffix means an exact copy. The passes that find the other kinds add one:

identifiers/ basket.js:1-9 ~ cart.js:1-9 (renamed)
inserted-line/ save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]
renamed-halves/ sync-contacts.js:1-13 ~ sync-leads.js:1-14 [~0.88 ast]

(renamed) is a copy that differs only in identifiers, literals or annotations, reported with --ignore-identifiers, --ignore-literals or --ignore-annotations. [~0.91 gap] is two exact clones merged across up to --max-gap-lines unmatched lines; the number is the share of matched tokens over the merged span. [~0.88 ast] is a pair of JavaScript or TypeScript functions whose syntax trees overlap at least --similarity; names and literal values do not count. Types of code clones explains the kinds.

With the other modes

The reporter keeps the same shape in the other modes, measured here on fixtures/dashboard-demo:

Terminal
jscpd . --reporters ai --summary --summary-top 3
Clones:
src/ checks.ts:8-13 ~ labels.ts:4-9
---
1 clones · 1.9% duplication
---
Summary by tokens (6 files, 3 folders):
files (tokens/lines/size/cx/dup%):
README.md 1289/163/8.0K/0/0.0%
src/rates.ts 182/35/757/11/0.0%
src/checks.ts 169/21/665/6/28.6%
folders (files/tokens/lines/size):
. 1/1289/163/8.0K
src 4/557/83/2.2K
src/legacy 1/113/10/430
time: 4.073ms
Terminal
jscpd . --reporters ai --complexity --summary-top 3
Complexity by complexity (6 files, 3 folders):
files (tokens/lines/size/cx):
src/rates.ts 182/35/757/11
src/checks.ts 169/21/665/6
src/labels.ts 109/11/409/3
folders (files/tokens/lines/size/mean cx):
src 4/557/83/2.2K/5
src/legacy 1/113/10/430/3
. 1/1289/163/8.0K/0
time: 3.009ms
Terminal
jscpd . --reporters ai --dead-code
basta dead code report — 2 findings across 5 files (14.0% of 93 lines)
Confidence is 0-100; anything below 90 has a listed reason it may be wrong. Verify a finding before deleting the code.
unused-file src/legacy/manifest.ts:1:1 confidence=95 src/legacy/manifest.ts is never imported and is not an entry point
unused-export src/checks.ts:19:17 confidence=85 exported function `checkBatch` is never imported

--health --reporters ai prints the one-line form shown under codebase-refactoring. To hand an agent one kind of clone, combine --reporters ai with --kind, for example --ignore-identifiers --kind renamed for the renamed copies alone. Reporters lists every reporter and the files the others write.

Limits

  • The MCP tools answer from the last scan. After the assistant edits files, check_current_directory is the call that refreshes them; it scans synchronously and can take seconds on a large project.
  • check_duplication needs a snippet of at least --min-tokens tokens (50 by default) and compares tokens, so it finds exact copies and, with the server's normalization options, renamed ones. The similarity argument works for JavaScript and TypeScript only.
  • The MCP server has no HTTP transport. jscpd-server, MCP over HTTP with a REST API, belongs to v4 and is maintained on the master-v4 branch; see v4.
  • The port skills need the embedding model or an embeddings API, and the first comparison of two folders embeds every function, which can take minutes. Semantic clones covers the model.
  • Editors shows the same findings as diagnostics while you type, through jscpd --lsp.
  • Reporters lists the reporters and the files they write.
  • Comparing two codebases explains the reports the two port skills read.
  • Dead code covers the second pass of codebase-refactoring.