codepage-bridge-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.claude-plugin/plugin.json +15 -0
  3. package/.mcp.json +11 -0
  4. package/LICENSE +21 -0
  5. package/README.md +990 -0
  6. package/README_CN.md +781 -0
  7. package/dist/src/core.d.ts +56 -0
  8. package/dist/src/core.js +166 -0
  9. package/dist/src/core.js.map +1 -0
  10. package/dist/src/diff.d.ts +8 -0
  11. package/dist/src/diff.js +11 -0
  12. package/dist/src/diff.js.map +1 -0
  13. package/dist/src/encoding/codec.d.ts +5 -0
  14. package/dist/src/encoding/codec.js +75 -0
  15. package/dist/src/encoding/codec.js.map +1 -0
  16. package/dist/src/encoding/index.d.ts +3 -0
  17. package/dist/src/encoding/index.js +4 -0
  18. package/dist/src/encoding/index.js.map +1 -0
  19. package/dist/src/encoding/rules.d.ts +4 -0
  20. package/dist/src/encoding/rules.js +70 -0
  21. package/dist/src/encoding/rules.js.map +1 -0
  22. package/dist/src/encoding/types.d.ts +22 -0
  23. package/dist/src/encoding/types.js +2 -0
  24. package/dist/src/encoding/types.js.map +1 -0
  25. package/dist/src/filesystem/atomic.d.ts +6 -0
  26. package/dist/src/filesystem/atomic.js +91 -0
  27. package/dist/src/filesystem/atomic.js.map +1 -0
  28. package/dist/src/filesystem/cache.d.ts +21 -0
  29. package/dist/src/filesystem/cache.js +40 -0
  30. package/dist/src/filesystem/cache.js.map +1 -0
  31. package/dist/src/filesystem/index.d.ts +4 -0
  32. package/dist/src/filesystem/index.js +5 -0
  33. package/dist/src/filesystem/index.js.map +1 -0
  34. package/dist/src/filesystem/mutex.d.ts +5 -0
  35. package/dist/src/filesystem/mutex.js +23 -0
  36. package/dist/src/filesystem/mutex.js.map +1 -0
  37. package/dist/src/filesystem/path.d.ts +9 -0
  38. package/dist/src/filesystem/path.js +53 -0
  39. package/dist/src/filesystem/path.js.map +1 -0
  40. package/dist/src/media/errors.d.ts +14 -0
  41. package/dist/src/media/errors.js +29 -0
  42. package/dist/src/media/errors.js.map +1 -0
  43. package/dist/src/media/image.d.ts +11 -0
  44. package/dist/src/media/image.js +81 -0
  45. package/dist/src/media/image.js.map +1 -0
  46. package/dist/src/media/index.d.ts +5 -0
  47. package/dist/src/media/index.js +6 -0
  48. package/dist/src/media/index.js.map +1 -0
  49. package/dist/src/media/notebook.d.ts +47 -0
  50. package/dist/src/media/notebook.js +122 -0
  51. package/dist/src/media/notebook.js.map +1 -0
  52. package/dist/src/media/pdf.d.ts +25 -0
  53. package/dist/src/media/pdf.js +104 -0
  54. package/dist/src/media/pdf.js.map +1 -0
  55. package/dist/src/media/types.d.ts +14 -0
  56. package/dist/src/media/types.js +2 -0
  57. package/dist/src/media/types.js.map +1 -0
  58. package/dist/src/prompts.d.ts +8 -0
  59. package/dist/src/prompts.js +46 -0
  60. package/dist/src/prompts.js.map +1 -0
  61. package/dist/src/schemas.d.ts +138 -0
  62. package/dist/src/schemas.js +81 -0
  63. package/dist/src/schemas.js.map +1 -0
  64. package/dist/src/server.d.ts +159 -0
  65. package/dist/src/server.js +98 -0
  66. package/dist/src/server.js.map +1 -0
  67. package/dist/src/toolTypes.d.ts +31 -0
  68. package/dist/src/toolTypes.js +2 -0
  69. package/dist/src/toolTypes.js.map +1 -0
  70. package/dist/src/tools/edit.d.ts +3 -0
  71. package/dist/src/tools/edit.js +168 -0
  72. package/dist/src/tools/edit.js.map +1 -0
  73. package/dist/src/tools/grep.d.ts +21 -0
  74. package/dist/src/tools/grep.js +192 -0
  75. package/dist/src/tools/grep.js.map +1 -0
  76. package/dist/src/tools/read.d.ts +3 -0
  77. package/dist/src/tools/read.js +137 -0
  78. package/dist/src/tools/read.js.map +1 -0
  79. package/dist/src/tools/write.d.ts +3 -0
  80. package/dist/src/tools/write.js +80 -0
  81. package/dist/src/tools/write.js.map +1 -0
  82. package/dist/src/validation.d.ts +6 -0
  83. package/dist/src/validation.js +42 -0
  84. package/dist/src/validation.js.map +1 -0
  85. package/examples/claude-config/settings.fragment.json +17 -0
  86. package/examples/minimal-project/.encoding-rules +10 -0
  87. package/examples/minimal-project/.mcp.json +11 -0
  88. package/examples/minimal-project/CLAUDE.md +15 -0
  89. package/install/download-release-unix.sh +72 -0
  90. package/install/download-release-windows.ps1 +61 -0
  91. package/install/install-from-release-unix.sh +18 -0
  92. package/install/install-from-release-windows.ps1 +19 -0
  93. package/install/install-from-source-unix.sh +44 -0
  94. package/install/install-from-source-windows.ps1 +45 -0
  95. package/install/install-this-release-unix.sh +37 -0
  96. package/install/install-this-release-windows.ps1 +36 -0
  97. package/package.json +64 -0
@@ -0,0 +1,80 @@
1
+ import { mkdir, stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { encodeText } from '../encoding/index.js';
4
+ import { getProjectFileContext, encodeSnapshotText, readDecodedFile, readRegistry } from '../core.js';
5
+ import { createStructuredPatch } from '../diff.js';
6
+ import { atomicWriteBuffer } from '../filesystem/index.js';
7
+ import { assertObject, rejectUnknown, requiredString } from '../validation.js';
8
+ export function parseWriteInput(value) {
9
+ assertObject(value);
10
+ rejectUnknown(value, ['file_path', 'content']);
11
+ return {
12
+ file_path: requiredString(value, 'file_path'),
13
+ content: requiredString(value, 'content'),
14
+ };
15
+ }
16
+ export async function executeWrite(input) {
17
+ const context = await getProjectFileContext(input.file_path);
18
+ let exists = true;
19
+ try {
20
+ await stat(context.absolutePath);
21
+ }
22
+ catch (error) {
23
+ if (error.code !== 'ENOENT')
24
+ throw error;
25
+ exists = false;
26
+ }
27
+ await mkdir(path.dirname(context.absolutePath), { recursive: true });
28
+ if (!exists) {
29
+ const buffer = encodeText(input.content, context.encoding);
30
+ await atomicWriteBuffer(context.root, context.absolutePath, buffer);
31
+ const info = await stat(context.absolutePath);
32
+ const snapshot = {
33
+ absolutePath: context.absolutePath,
34
+ root: context.root,
35
+ encoding: context.encoding,
36
+ text: '',
37
+ bom: null,
38
+ newline: null,
39
+ mtimeMs: 0,
40
+ size: 0,
41
+ hash: '',
42
+ complete: true,
43
+ };
44
+ readRegistry.updateAfterWrite(snapshot, input.content, buffer, info.mtimeMs);
45
+ return {
46
+ content: [{ type: 'text', text: `File created successfully at: ${context.absolutePath}` }],
47
+ structuredContent: {
48
+ type: 'create',
49
+ filePath: context.absolutePath,
50
+ content: input.content,
51
+ structuredPatch: createStructuredPatch(context.absolutePath, '', input.content),
52
+ originalFile: null,
53
+ },
54
+ };
55
+ }
56
+ const readSnapshot = readRegistry.get(context.absolutePath);
57
+ if (!readSnapshot)
58
+ throw new Error('File has not been read. Read it before attempting to write it.');
59
+ const current = await readDecodedFile(context);
60
+ if ((current.hash !== readSnapshot.hash || current.mtimeMs !== readSnapshot.mtimeMs) && current.text !== readSnapshot.text) {
61
+ throw new Error('File has been unexpectedly modified. Read it again before attempting to write it.');
62
+ }
63
+ const buffer = encodeSnapshotText(current, input.content, false);
64
+ await atomicWriteBuffer(context.root, context.absolutePath, buffer, {
65
+ expected: { mtimeMs: current.mtimeMs, hash: current.hash },
66
+ });
67
+ const info = await stat(context.absolutePath);
68
+ readRegistry.updateAfterWrite(current, input.content, buffer, info.mtimeMs);
69
+ return {
70
+ content: [{ type: 'text', text: `The file ${context.absolutePath} has been updated successfully.` }],
71
+ structuredContent: {
72
+ type: 'update',
73
+ filePath: context.absolutePath,
74
+ content: input.content,
75
+ structuredPatch: createStructuredPatch(context.absolutePath, current.text, input.content),
76
+ originalFile: current.text,
77
+ },
78
+ };
79
+ }
80
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.js","sourceRoot":"","sources":["../../../src/tools/write.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function assertObject(value: unknown): asserts value is Record<string, unknown>;
2
+ export declare function rejectUnknown(input: Record<string, unknown>, allowed: readonly string[]): void;
3
+ export declare function requiredString(input: Record<string, unknown>, key: string): string;
4
+ export declare function optionalString(input: Record<string, unknown>, key: string): string | undefined;
5
+ export declare function optionalInteger(input: Record<string, unknown>, key: string, minimum: number): number | undefined;
6
+ export declare function optionalBoolean(input: Record<string, unknown>, key: string): boolean | undefined;
@@ -0,0 +1,42 @@
1
+ export function assertObject(value) {
2
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
3
+ throw new Error('Tool input must be an object');
4
+ }
5
+ }
6
+ export function rejectUnknown(input, allowed) {
7
+ const unknown = Object.keys(input).filter(key => !allowed.includes(key));
8
+ if (unknown.length > 0)
9
+ throw new Error(`Unknown input field(s): ${unknown.join(', ')}`);
10
+ }
11
+ export function requiredString(input, key) {
12
+ const value = input[key];
13
+ if (typeof value !== 'string')
14
+ throw new Error(`${key} must be a string`);
15
+ return value;
16
+ }
17
+ export function optionalString(input, key) {
18
+ const value = input[key];
19
+ if (value === undefined)
20
+ return undefined;
21
+ if (typeof value !== 'string')
22
+ throw new Error(`${key} must be a string`);
23
+ return value;
24
+ }
25
+ export function optionalInteger(input, key, minimum) {
26
+ const value = input[key];
27
+ if (value === undefined)
28
+ return undefined;
29
+ if (!Number.isInteger(value) || value < minimum) {
30
+ throw new Error(`${key} must be an integer greater than or equal to ${minimum}`);
31
+ }
32
+ return value;
33
+ }
34
+ export function optionalBoolean(input, key) {
35
+ const value = input[key];
36
+ if (value === undefined)
37
+ return undefined;
38
+ if (typeof value !== 'boolean')
39
+ throw new Error(`${key} must be a boolean`);
40
+ return value;
41
+ }
42
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/validation.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;IACjD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA8B,EAAE,OAA0B;IACtF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;IACxE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAC1F,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA8B,EAAE,GAAW;IACxE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,mBAAmB,CAAC,CAAA;IACzE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA8B,EAAE,GAAW;IACxE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,mBAAmB,CAAC,CAAA;IACzE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,KAA8B,EAC9B,GAAW,EACX,OAAe;IAEf,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAK,KAAgB,GAAG,OAAO,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,gDAAgD,OAAO,EAAE,CAAC,CAAA;IAClF,CAAC;IACD,OAAO,KAAe,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA8B,EAAE,GAAW;IACzE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,oBAAoB,CAAC,CAAA;IAC3E,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,17 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "mcp__codepage-bridge__Read",
5
+ "mcp__codepage-bridge__Grep",
6
+ "mcp__codepage-bridge__Edit",
7
+ "mcp__codepage-bridge__Write"
8
+ ],
9
+ "deny": [
10
+ "Read",
11
+ "Grep",
12
+ "Edit",
13
+ "Write",
14
+ "NotebookEdit"
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,10 @@
1
+ # Minimal project encoding rules
2
+ # Last matching rule wins
3
+
4
+ *.c gbk
5
+ *.cpp gbk
6
+ *.h gbk
7
+ **/*.json utf8
8
+
9
+ # Example: generated files remain strict UTF-8
10
+ !SourceCode/generated/**
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "codepage-bridge": {
4
+ "type": "stdio",
5
+ "command": "node",
6
+ "args": [
7
+ "/absolute/path/to/codepage-bridge-mcp/dist/src/server.js"
8
+ ]
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,15 @@
1
+ ## File encoding policy
2
+
3
+ Use Codepage Bridge for all project file content operations:
4
+
5
+ - Read with `mcp__codepage-bridge__Read`.
6
+ - Search with `mcp__codepage-bridge__Grep`.
7
+ - Edit with `mcp__codepage-bridge__Edit`.
8
+ - Create or completely rewrite with `mcp__codepage-bridge__Write`.
9
+
10
+ Do not use built-in Read, Grep, Edit, Write, NotebookEdit, shell commands,
11
+ PowerShell commands, or scripts as substitutes for project file content access.
12
+ Glob may only be used to discover paths.
13
+
14
+ Do not manually transcode files or normalize line endings. `.encoding-rules`
15
+ is the source of truth.
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ VERSION="${1:-latest}"
5
+ SERVER_NAME="${SERVER_NAME:-codepage-bridge}"
6
+ SCOPE="${SCOPE:-user}"
7
+ REPO="skyispainted/codepage-bridge-mcp"
8
+ INSTALL_ROOT="${HOME}/.codepage-bridge"
9
+ RELEASE_ROOT="${INSTALL_ROOT}/releases"
10
+
11
+ require_command() {
12
+ command -v "$1" >/dev/null 2>&1 || {
13
+ echo "Required command not found: $1" >&2
14
+ exit 1
15
+ }
16
+ }
17
+
18
+ require_command node
19
+ require_command claude
20
+ require_command curl
21
+ require_command tar
22
+ require_command uname
23
+
24
+ mkdir -p "$RELEASE_ROOT"
25
+
26
+ platform="$(uname -s)"
27
+ arch="$(uname -m)"
28
+ case "$platform" in
29
+ Linux) os="linux" ;;
30
+ Darwin) os="darwin" ;;
31
+ *) echo "Unsupported platform: $platform" >&2; exit 1 ;;
32
+ esac
33
+ case "$arch" in
34
+ x86_64|amd64) cpu="x64" ;;
35
+ arm64|aarch64) cpu="arm64" ;;
36
+ *) echo "Unsupported architecture: $arch" >&2; exit 1 ;;
37
+ esac
38
+
39
+ if [ "$VERSION" = "latest" ]; then
40
+ api_url="https://api.github.com/repos/$REPO/releases/latest"
41
+ else
42
+ api_url="https://api.github.com/repos/$REPO/releases/tags/$VERSION"
43
+ fi
44
+
45
+ release_json="$(curl -fsSL -H 'User-Agent: codepage-bridge-installer' "$api_url")"
46
+ tag="$(printf '%s' "$release_json" | node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync(0,'utf8')); process.stdout.write(data.tag_name)")"
47
+ asset_name="codepage-bridge-${tag}-${os}-${cpu}.tar.gz"
48
+ asset_url="$(printf '%s' "$release_json" | node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync(0,'utf8')); const asset=data.assets.find(a=>a.name===process.argv[1]); if(!asset){process.exit(2)} process.stdout.write(asset.browser_download_url)" "$asset_name")" || {
49
+ echo "Release asset not found: $asset_name" >&2
50
+ exit 1
51
+ }
52
+
53
+ archive="$INSTALL_ROOT/$asset_name"
54
+ target_dir="$RELEASE_ROOT/$tag"
55
+ rm -rf "$target_dir"
56
+ mkdir -p "$target_dir"
57
+
58
+ echo "[1/3] Downloading $asset_name ..."
59
+ curl -fsSL -H 'User-Agent: codepage-bridge-installer' "$asset_url" -o "$archive"
60
+
61
+ echo "[2/3] Extracting release ..."
62
+ tar -xzf "$archive" -C "$target_dir"
63
+ rm -f "$archive"
64
+
65
+ package_root="$target_dir/codepage-bridge-${tag}-${os}-${cpu}"
66
+ if [ ! -d "$package_root" ]; then
67
+ echo "Expected extracted package directory not found: $package_root" >&2
68
+ exit 1
69
+ fi
70
+
71
+ echo "[3/3] Installing extracted release package ..."
72
+ bash "$package_root/install/install-this-release-unix.sh" "$SERVER_NAME"
@@ -0,0 +1,61 @@
1
+ param(
2
+ [string]$Version = 'latest',
3
+ [string]$ServerName = 'codepage-bridge',
4
+ [string]$Scope = 'user'
5
+ )
6
+
7
+ $ErrorActionPreference = 'Stop'
8
+
9
+ function Require-Command($name) {
10
+ if (-not (Get-Command $name -ErrorAction SilentlyContinue)) {
11
+ throw "Required command not found: $name"
12
+ }
13
+ }
14
+
15
+ Require-Command node
16
+ Require-Command claude
17
+ Require-Command powershell
18
+
19
+ $Repo = 'skyispainted/codepage-bridge-mcp'
20
+ $InstallRoot = Join-Path $env:USERPROFILE '.codepage-bridge'
21
+ $ReleaseRoot = Join-Path $InstallRoot 'releases'
22
+ New-Item -ItemType Directory -Force -Path $ReleaseRoot | Out-Null
23
+
24
+ function Get-ReleaseInfo {
25
+ param([string]$Version)
26
+ if ($Version -eq 'latest') {
27
+ $url = "https://api.github.com/repos/$Repo/releases/latest"
28
+ } else {
29
+ $url = "https://api.github.com/repos/$Repo/releases/tags/$Version"
30
+ }
31
+ Invoke-RestMethod -Uri $url -Headers @{ 'User-Agent' = 'codepage-bridge-installer' }
32
+ }
33
+
34
+ $release = Get-ReleaseInfo -Version $Version
35
+ $tag = $release.tag_name
36
+ $assetName = "codepage-bridge-$tag-win32-x64.zip"
37
+ $asset = $release.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
38
+ if (-not $asset) {
39
+ throw "Release asset not found: $assetName"
40
+ }
41
+
42
+ $zipPath = Join-Path $InstallRoot $assetName
43
+ $targetDir = Join-Path $ReleaseRoot $tag
44
+ if (Test-Path $targetDir) {
45
+ Remove-Item -Recurse -Force $targetDir
46
+ }
47
+
48
+ Write-Host "[1/3] Downloading $assetName ..."
49
+ Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $zipPath -Headers @{ 'User-Agent' = 'codepage-bridge-installer' }
50
+
51
+ Write-Host "[2/3] Extracting release ..."
52
+ Expand-Archive -Path $zipPath -DestinationPath $targetDir -Force
53
+ Remove-Item -Force $zipPath
54
+
55
+ $packageRoot = Join-Path $targetDir "codepage-bridge-$tag-win32-x64"
56
+ if (-not (Test-Path $packageRoot)) {
57
+ throw "Expected extracted package directory not found: $packageRoot"
58
+ }
59
+
60
+ Write-Host "[3/3] Installing extracted release package ..."
61
+ & (Join-Path $packageRoot 'install\install-this-release-windows.ps1') -ServerName $ServerName -Scope $Scope
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ VERSION="${1:-latest}"
5
+ SERVER_NAME="${SERVER_NAME:-codepage-bridge}"
6
+ SCOPE="${SCOPE:-user}"
7
+
8
+ PACKAGE_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9
+ ENTRY="$PACKAGE_ROOT/dist/src/server.js"
10
+
11
+ if [ -f "$ENTRY" ]; then
12
+ echo "Detected an extracted release package in the current directory. Installing from local files..."
13
+ bash "$PACKAGE_ROOT/install/install-this-release-unix.sh" "$SERVER_NAME"
14
+ exit 0
15
+ fi
16
+
17
+ echo "No local extracted release package detected. Downloading from GitHub Release..."
18
+ SCOPE="$SCOPE" SERVER_NAME="$SERVER_NAME" bash "$PACKAGE_ROOT/install/download-release-unix.sh" "$VERSION"
@@ -0,0 +1,19 @@
1
+ param(
2
+ [string]$Version = 'latest',
3
+ [string]$ServerName = 'codepage-bridge',
4
+ [string]$Scope = 'user'
5
+ )
6
+
7
+ $ErrorActionPreference = 'Stop'
8
+
9
+ $packageRoot = Split-Path -Parent $PSScriptRoot
10
+ $entry = Join-Path $packageRoot 'dist\src\server.js'
11
+
12
+ if (Test-Path $entry) {
13
+ Write-Host 'Detected an extracted release package in the current directory. Installing from local files...'
14
+ & (Join-Path $PSScriptRoot 'install-this-release-windows.ps1') -ServerName $ServerName -Scope $Scope
15
+ exit 0
16
+ }
17
+
18
+ Write-Host 'No local extracted release package detected. Downloading from GitHub Release...'
19
+ & (Join-Path $PSScriptRoot 'download-release-windows.ps1') -Version $Version -ServerName $ServerName -Scope $Scope
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SERVER_NAME="${1:-codepage-bridge}"
5
+ SCOPE="${SCOPE:-user}"
6
+
7
+ require_command() {
8
+ command -v "$1" >/dev/null 2>&1 || {
9
+ echo "Required command not found: $1" >&2
10
+ exit 1
11
+ }
12
+ }
13
+
14
+ require_command node
15
+ require_command npm
16
+ require_command claude
17
+
18
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
19
+ ENTRY="$REPO_ROOT/dist/src/server.js"
20
+
21
+ echo "[1/4] Installing npm dependencies..."
22
+ npm --prefix "$REPO_ROOT" install
23
+
24
+ echo "[2/4] Building Codepage Bridge..."
25
+ npm --prefix "$REPO_ROOT" run build
26
+
27
+ if [ ! -f "$ENTRY" ]; then
28
+ echo "Build did not produce $ENTRY" >&2
29
+ exit 1
30
+ fi
31
+
32
+ echo "[3/4] Registering Claude Code MCP..."
33
+ claude mcp remove "$SERVER_NAME" --scope "$SCOPE" >/dev/null 2>&1 || true
34
+ claude mcp add --scope "$SCOPE" "$SERVER_NAME" -- node "$ENTRY"
35
+
36
+ echo "[4/4] Verifying MCP connection..."
37
+ claude mcp get "$SERVER_NAME"
38
+
39
+ echo
40
+ echo "Next required steps:"
41
+ echo "1. Merge examples/claude-config/settings.fragment.json into ~/.claude/settings.json"
42
+ echo "2. Add examples/minimal-project/CLAUDE.md to your project or ~/.claude/CLAUDE.md"
43
+ echo "3. Add a .encoding-rules file to each legacy-encoded project"
44
+ echo "4. Start a new Claude Code session and verify it uses mcp__codepage-bridge__Read/Grep/Edit/Write"
@@ -0,0 +1,45 @@
1
+ param(
2
+ [string]$ServerName = "codepage-bridge",
3
+ [string]$Scope = "user"
4
+ )
5
+
6
+ $ErrorActionPreference = "Stop"
7
+
8
+ function Require-Command($name) {
9
+ if (-not (Get-Command $name -ErrorAction SilentlyContinue)) {
10
+ throw "Required command not found: $name"
11
+ }
12
+ }
13
+
14
+ Require-Command node
15
+ Require-Command npm
16
+ Require-Command claude
17
+
18
+ $repoRoot = Split-Path -Parent $PSScriptRoot
19
+ $entry = Join-Path $repoRoot "dist\src\server.js"
20
+
21
+ Write-Host "[1/4] Installing npm dependencies..."
22
+ npm --prefix $repoRoot install
23
+
24
+ Write-Host "[2/4] Building Codepage Bridge..."
25
+ npm --prefix $repoRoot run build
26
+
27
+ if (-not (Test-Path $entry)) {
28
+ throw "Build did not produce $entry"
29
+ }
30
+
31
+ Write-Host "[3/4] Registering Claude Code MCP..."
32
+ try {
33
+ claude mcp remove $ServerName --scope $Scope | Out-Null
34
+ } catch {}
35
+ claude mcp add --scope $Scope $ServerName -- node $entry
36
+
37
+ Write-Host "[4/4] Verifying MCP connection..."
38
+ claude mcp get $ServerName
39
+
40
+ Write-Host ""
41
+ Write-Host "Next required steps:" -ForegroundColor Yellow
42
+ Write-Host "1. Merge examples/claude-config/settings.fragment.json into ~/.claude/settings.json"
43
+ Write-Host "2. Add examples/minimal-project/CLAUDE.md to your project or ~/.claude/CLAUDE.md"
44
+ Write-Host "3. Add a .encoding-rules file to each legacy-encoded project"
45
+ Write-Host "4. Start a new Claude Code session and verify it uses mcp__codepage-bridge__Read/Grep/Edit/Write"
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SERVER_NAME="${1:-codepage-bridge}"
5
+ SCOPE="${SCOPE:-user}"
6
+
7
+ require_command() {
8
+ command -v "$1" >/dev/null 2>&1 || {
9
+ echo "Required command not found: $1" >&2
10
+ exit 1
11
+ }
12
+ }
13
+
14
+ require_command node
15
+ require_command claude
16
+
17
+ PACKAGE_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
18
+ ENTRY="$PACKAGE_ROOT/dist/src/server.js"
19
+
20
+ if [ ! -f "$ENTRY" ]; then
21
+ echo "This directory does not look like an extracted Codepage Bridge release package. Missing: $ENTRY" >&2
22
+ exit 1
23
+ fi
24
+
25
+ echo "[1/2] Registering Claude Code MCP from this release package..."
26
+ claude mcp remove "$SERVER_NAME" --scope "$SCOPE" >/dev/null 2>&1 || true
27
+ claude mcp add --scope "$SCOPE" "$SERVER_NAME" -- node "$ENTRY"
28
+
29
+ echo "[2/2] Verifying MCP connection..."
30
+ claude mcp get "$SERVER_NAME"
31
+
32
+ echo
33
+ echo "Installed $SERVER_NAME from the current extracted release package."
34
+ echo "Next required steps:"
35
+ echo "1. Merge examples/claude-config/settings.fragment.json into ~/.claude/settings.json"
36
+ echo "2. Add CLAUDE.md policy from examples/minimal-project/CLAUDE.md"
37
+ echo "3. Add a .encoding-rules file to each legacy-encoded project"
@@ -0,0 +1,36 @@
1
+ param(
2
+ [string]$ServerName = 'codepage-bridge',
3
+ [string]$Scope = 'user'
4
+ )
5
+
6
+ $ErrorActionPreference = 'Stop'
7
+
8
+ function Require-Command($name) {
9
+ if (-not (Get-Command $name -ErrorAction SilentlyContinue)) {
10
+ throw "Required command not found: $name"
11
+ }
12
+ }
13
+
14
+ Require-Command node
15
+ Require-Command claude
16
+
17
+ $packageRoot = Split-Path -Parent $PSScriptRoot
18
+ $entry = Join-Path $packageRoot 'dist\src\server.js'
19
+
20
+ if (-not (Test-Path $entry)) {
21
+ throw "This directory does not look like an extracted Codepage Bridge release package. Missing: $entry"
22
+ }
23
+
24
+ Write-Host "[1/2] Registering Claude Code MCP from this release package..."
25
+ try { claude mcp remove $ServerName --scope $Scope | Out-Null } catch {}
26
+ claude mcp add --scope $Scope $ServerName -- node $entry
27
+
28
+ Write-Host "[2/2] Verifying MCP connection..."
29
+ claude mcp get $ServerName
30
+
31
+ Write-Host ""
32
+ Write-Host "Installed $ServerName from the current extracted release package." -ForegroundColor Green
33
+ Write-Host "Next required steps:" -ForegroundColor Yellow
34
+ Write-Host "1. Merge examples/claude-config/settings.fragment.json into ~/.claude/settings.json"
35
+ Write-Host "2. Add CLAUDE.md policy from examples/minimal-project/CLAUDE.md"
36
+ Write-Host "3. Add a .encoding-rules file to each legacy-encoded project"
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "codepage-bridge-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Encoding-transparent MCP file tools for legacy codepages in Claude Code",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "codepage-bridge-mcp": "dist/src/server.js"
9
+ },
10
+ "files": [
11
+ "dist/src/",
12
+ "LICENSE",
13
+ "README.md",
14
+ "README_CN.md",
15
+ "examples/",
16
+ "install/",
17
+ ".claude-plugin/",
18
+ ".mcp.json"
19
+ ],
20
+ "scripts": {
21
+ "clean": "node -e \"const fs=require('fs'); fs.rmSync('dist',{recursive:true,force:true})\"",
22
+ "build": "tsc -p tsconfig.json",
23
+ "check": "tsc -p tsconfig.json --noEmit",
24
+ "test": "vitest run",
25
+ "start": "node dist/src/server.js",
26
+ "prepublishOnly": "npm run check && npm test && npm run build"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/skyispainted/codepage-bridge-mcp.git"
31
+ },
32
+ "homepage": "https://github.com/skyispainted/codepage-bridge-mcp#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/skyispainted/codepage-bridge-mcp/issues"
35
+ },
36
+ "keywords": [
37
+ "claude-code",
38
+ "mcp",
39
+ "encoding",
40
+ "gbk",
41
+ "legacy-codepage",
42
+ "grep",
43
+ "read",
44
+ "edit",
45
+ "write"
46
+ ],
47
+ "engines": {
48
+ "node": ">=20"
49
+ },
50
+ "dependencies": {
51
+ "@modelcontextprotocol/sdk": "^1.17.0",
52
+ "diff": "^8.0.2",
53
+ "iconv-lite": "^0.6.3",
54
+ "picomatch": "^4.0.3",
55
+ "sharp": "^0.34.3"
56
+ },
57
+ "devDependencies": {
58
+ "@types/diff": "^7.0.2",
59
+ "@types/node": "^24.0.15",
60
+ "@types/picomatch": "^4.0.2",
61
+ "typescript": "^5.8.3",
62
+ "vitest": "^3.2.4"
63
+ }
64
+ }