ts-mcp-server 1.0.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.
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +140 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ts-mcp-server contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# ts-mcp-server
|
|
2
|
+
|
|
3
|
+
A lightweight [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server for **TypeScript and JavaScript refactoring**. Rename or move files and folders across your codebase and have every `import`, `require`, and re-export updated automatically — powered by the same TypeScript language service that drives VS Code.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
AI coding assistants can read and write code, but they struggle with **structural changes** that ripple across many files. Moving a React component, renaming a utility module, or reorganizing a folder means updating every import that references it. Miss one and the build breaks.
|
|
8
|
+
|
|
9
|
+
`ts-mcp-server` gives any MCP-compatible client — [VS Code Copilot](https://code.visualstudio.com/), [Claude Desktop](https://claude.ai/), [Cursor](https://cursor.sh/), [Windsurf](https://codeium.com/windsurf), [Continue](https://continue.dev/), and others — the ability to perform these refactors **correctly and completely**, using TypeScript's own compiler infrastructure.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Rename / move files** — `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, `.cts`, `.mjs`, `.cjs`
|
|
14
|
+
- **Rename / move folders** — all imports referencing files inside the folder are updated
|
|
15
|
+
- **Automatic project discovery** — `tsconfig.json` is detected automatically; no configuration needed
|
|
16
|
+
- **Multi-project support** — monorepos, project references, and composite builds work out of the box
|
|
17
|
+
- **Preview mode** — see exactly what would change before applying anything
|
|
18
|
+
- **Cross-platform** — Windows, macOS, and Linux
|
|
19
|
+
|
|
20
|
+
## How It Works
|
|
21
|
+
|
|
22
|
+
Under the hood, `ts-mcp-server` communicates with TypeScript's `tsserver` over Node IPC. When you rename a file or folder, it calls [`getEditsForFileRename`](https://github.com/microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29) — the same API that VS Code uses — to compute every import path that needs updating, applies the edits to disk, then moves the file or folder.
|
|
23
|
+
|
|
24
|
+
There is no regex, no custom path resolution, no heuristics. The TypeScript compiler does all the work.
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx ts-mcp-server
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Configure Your MCP Client
|
|
35
|
+
|
|
36
|
+
Add `ts-mcp-server` to your client's MCP configuration.
|
|
37
|
+
|
|
38
|
+
**VS Code** (`.vscode/mcp.json`):
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"servers": {
|
|
43
|
+
"ts-mcp-server": {
|
|
44
|
+
"command": "npx",
|
|
45
|
+
"args": ["ts-mcp-server"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Claude Desktop** (`claude_desktop_config.json`):
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"ts-mcp-server": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["ts-mcp-server"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Cursor, Windsurf, Continue** — follow each client's MCP server documentation using the same `npx ts-mcp-server` command.
|
|
65
|
+
|
|
66
|
+
## Tool Reference
|
|
67
|
+
|
|
68
|
+
### `rename_file_or_dir`
|
|
69
|
+
|
|
70
|
+
Rename or move a TypeScript/JavaScript file or folder and update all import paths across the project.
|
|
71
|
+
|
|
72
|
+
| Parameter | Type | Required | Description |
|
|
73
|
+
| --------- | --------- | -------- | --------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `from` | `string` | ✅ | Current file or folder path (absolute or relative to cwd) |
|
|
75
|
+
| `to` | `string` | ✅ | New file or folder path (absolute or relative to cwd) |
|
|
76
|
+
| `preview` | `boolean` | — | If `true`, returns a summary of what would change without applying anything. Default: `false` |
|
|
77
|
+
|
|
78
|
+
**Examples:**
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
# Rename a file
|
|
82
|
+
rename_file_or_dir from="src/utils/helpers.ts" to="src/utils/string-helpers.ts"
|
|
83
|
+
|
|
84
|
+
# Move a file to a different directory
|
|
85
|
+
rename_file_or_dir from="src/Button.tsx" to="src/components/ui/Button.tsx"
|
|
86
|
+
|
|
87
|
+
# Rename a folder
|
|
88
|
+
rename_file_or_dir from="src/components/primitives" to="src/components/ui"
|
|
89
|
+
|
|
90
|
+
# Preview changes without applying
|
|
91
|
+
rename_file_or_dir from="src/old-name.ts" to="src/new-name.ts" preview=true
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Supported Languages & Frameworks
|
|
95
|
+
|
|
96
|
+
`ts-mcp-server` works with any project that TypeScript's language service understands:
|
|
97
|
+
|
|
98
|
+
- **TypeScript** (`.ts`, `.tsx`, `.mts`, `.cts`)
|
|
99
|
+
- **JavaScript** (`.js`, `.jsx`, `.mjs`, `.cjs`)
|
|
100
|
+
- **React** / **Next.js** / **Remix** / **Astro**
|
|
101
|
+
- **Vue** (script blocks)
|
|
102
|
+
- **Node.js** / **Express** / **Fastify** / **NestJS**
|
|
103
|
+
- **Angular**
|
|
104
|
+
- **Svelte** (script blocks)
|
|
105
|
+
- **Electron**
|
|
106
|
+
- **React Native**
|
|
107
|
+
- **Monorepos** (Turborepo, Nx, Lerna, pnpm workspaces)
|
|
108
|
+
|
|
109
|
+
If your project has a `tsconfig.json` (or `jsconfig.json`), it works.
|
|
110
|
+
|
|
111
|
+
## System Requirements
|
|
112
|
+
|
|
113
|
+
| Requirement | Version |
|
|
114
|
+
| -------------- | --------------------------------------------- |
|
|
115
|
+
| **Node.js** | 22 or later (current LTS) |
|
|
116
|
+
| **TypeScript** | 5.x (installed automatically as a dependency) |
|
|
117
|
+
| **OS** | Windows, macOS, Linux |
|
|
118
|
+
|
|
119
|
+
No additional dependencies or global tools are required. The server bundles everything it needs.
|
|
120
|
+
|
|
121
|
+
## FAQ
|
|
122
|
+
|
|
123
|
+
**Does it work without a `tsconfig.json`?**
|
|
124
|
+
Yes. TypeScript will create an inferred project, but explicit configuration gives better results.
|
|
125
|
+
|
|
126
|
+
**Does it update `package.json` or non-code files?**
|
|
127
|
+
No. It updates TypeScript/JavaScript import and export statements, and path-related entries in `tsconfig.json` (`files`, `include`, `exclude`, `paths`).
|
|
128
|
+
|
|
129
|
+
**Can I use it with JavaScript-only projects?**
|
|
130
|
+
Yes. Add a `jsconfig.json` (which is equivalent to `tsconfig.json` with `allowJs: true`) and the server will discover your project.
|
|
131
|
+
|
|
132
|
+
**Does it work with path aliases (`@/components/...`)?**
|
|
133
|
+
Yes. `tsserver` resolves path aliases defined in `tsconfig.json`'s `paths` and `baseUrl` settings.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { fork } from "child_process";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, } from "fs";
|
|
7
|
+
import { dirname, resolve, join } from "path";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
function toAbsolutePath(base, input) {
|
|
10
|
+
return resolve(base, input);
|
|
11
|
+
}
|
|
12
|
+
let seq = 0;
|
|
13
|
+
const pending = new Map();
|
|
14
|
+
const tsserver = fork(createRequire(import.meta.url).resolve("typescript/lib/tsserver.js"), ["--useNodeIpc", "--disableAutomaticTypingAcquisition"], { silent: true });
|
|
15
|
+
tsserver.on("message", (msg) => {
|
|
16
|
+
if (msg.type === "response" && msg.request_seq !== undefined) {
|
|
17
|
+
const p = pending.get(msg.request_seq);
|
|
18
|
+
if (p) {
|
|
19
|
+
pending.delete(msg.request_seq);
|
|
20
|
+
msg.success ? p.resolve(msg.body) : p.reject(new Error(msg.message));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
function send(command, args) {
|
|
25
|
+
const id = ++seq;
|
|
26
|
+
tsserver.send({ seq: id, type: "request", command, arguments: args });
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
pending.set(id, { resolve: resolve, reject });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// ── Query: ensure project is loaded, then ask for rename edits ──
|
|
32
|
+
//
|
|
33
|
+
// tsserver is an editor service — it only knows about projects whose
|
|
34
|
+
// files have been "opened." We handle that here so callers never
|
|
35
|
+
// need to think about it.
|
|
36
|
+
async function getEditsForRename(oldPath, newPath, fileToOpen) {
|
|
37
|
+
await send("open", { file: fileToOpen });
|
|
38
|
+
return send("getEditsForFileRename-full", {
|
|
39
|
+
oldFilePath: oldPath,
|
|
40
|
+
newFilePath: newPath,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// ── Apply edits to file text (bottom-up to preserve offsets) ────
|
|
44
|
+
function applyEdits(text, edits) {
|
|
45
|
+
return edits.reduceRight((t, { span, newText }) => t.slice(0, span.start) + newText + t.slice(span.start + span.length), text);
|
|
46
|
+
}
|
|
47
|
+
// ── Boot ────────────────────────────────────────────────────────
|
|
48
|
+
const server = new McpServer({
|
|
49
|
+
name: "ts-mcp-server",
|
|
50
|
+
version: "1.0.0",
|
|
51
|
+
});
|
|
52
|
+
server.registerTool("rename_file_or_dir", {
|
|
53
|
+
title: "Rename / Move File or Folder",
|
|
54
|
+
description: "Rename or move a TypeScript/JavaScript file OR folder and automatically update all import paths across the project. Supports .ts, .tsx, .js, .jsx. tsserver auto-discovers the relevant tsconfig.json. For folders, all imports referencing files inside the folder are updated.",
|
|
55
|
+
inputSchema: z.object({
|
|
56
|
+
from: z
|
|
57
|
+
.string()
|
|
58
|
+
.describe("Current file or folder path (absolute or relative to cwd)"),
|
|
59
|
+
to: z
|
|
60
|
+
.string()
|
|
61
|
+
.describe("New file or folder path (absolute or relative to cwd)"),
|
|
62
|
+
preview: z
|
|
63
|
+
.boolean()
|
|
64
|
+
.optional()
|
|
65
|
+
.default(false)
|
|
66
|
+
.describe("If true, show what would change without applying anything"),
|
|
67
|
+
}),
|
|
68
|
+
}, async ({ from, to, preview }) => {
|
|
69
|
+
try {
|
|
70
|
+
const oldPath = toAbsolutePath(process.cwd(), from);
|
|
71
|
+
const newPath = toAbsolutePath(process.cwd(), to);
|
|
72
|
+
const stat = statSync(oldPath, { throwIfNoEntry: false });
|
|
73
|
+
if (!stat) {
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: `Not found: ${oldPath}` }],
|
|
76
|
+
isError: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (existsSync(newPath)) {
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{ type: "text", text: `Destination already exists: ${newPath}` },
|
|
83
|
+
],
|
|
84
|
+
isError: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const isDir = stat.isDirectory();
|
|
88
|
+
const fileToOpen = isDir ?
|
|
89
|
+
readdirSync(oldPath, { withFileTypes: true }).find((e) => e.isFile())
|
|
90
|
+
: undefined;
|
|
91
|
+
if (isDir && !fileToOpen) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: `No files found in ${oldPath}` }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const edits = await getEditsForRename(oldPath, newPath, isDir ? join(oldPath, fileToOpen.name) : oldPath);
|
|
98
|
+
const withChanges = edits.filter((e) => e.textChanges.length > 0);
|
|
99
|
+
if (preview) {
|
|
100
|
+
const totalEdits = withChanges.reduce((n, e) => n + e.textChanges.length, 0);
|
|
101
|
+
const summary = withChanges.length === 0 ?
|
|
102
|
+
`No import updates needed.`
|
|
103
|
+
: `Would update ${totalEdits} import(s) across ${withChanges.length} file(s).`;
|
|
104
|
+
return { content: [{ type: "text", text: summary }] };
|
|
105
|
+
}
|
|
106
|
+
// Apply edits to each affected file on disk
|
|
107
|
+
const updated = [];
|
|
108
|
+
for (const fileEdit of withChanges) {
|
|
109
|
+
try {
|
|
110
|
+
const original = readFileSync(fileEdit.fileName, "utf-8");
|
|
111
|
+
writeFileSync(fileEdit.fileName, applyEdits(original, fileEdit.textChanges), "utf-8");
|
|
112
|
+
updated.push(fileEdit.fileName);
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: "text",
|
|
119
|
+
text: `Error updating ${fileEdit.fileName}: ${e}`,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Move the file/folder itself
|
|
127
|
+
mkdirSync(dirname(newPath), { recursive: true });
|
|
128
|
+
renameSync(oldPath, newPath);
|
|
129
|
+
const what = isDir ? "folder" : "file";
|
|
130
|
+
const summary = updated.length > 0 ?
|
|
131
|
+
`Done. Renamed ${what}. Updated imports in ${updated.length} file(s).`
|
|
132
|
+
: `Done. Renamed ${what}. No import updates needed.`;
|
|
133
|
+
return { content: [{ type: "text", text: summary }] };
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const transport = new StdioServerTransport();
|
|
140
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server that renames/moves TypeScript files and updates all imports via tsserver",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ts-mcp-server": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"prepublishOnly": "tsc"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"mcp-server",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"typescript",
|
|
22
|
+
"javascript",
|
|
23
|
+
"react",
|
|
24
|
+
"tsx",
|
|
25
|
+
"jsx",
|
|
26
|
+
"refactor",
|
|
27
|
+
"refactoring",
|
|
28
|
+
"rename",
|
|
29
|
+
"move-file",
|
|
30
|
+
"import-update",
|
|
31
|
+
"tsserver",
|
|
32
|
+
"language-server",
|
|
33
|
+
"code-automation",
|
|
34
|
+
"ai-tools",
|
|
35
|
+
"copilot",
|
|
36
|
+
"claude",
|
|
37
|
+
"cursor"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"author": "andyliner",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
46
|
+
"typescript": "^5"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^22"
|
|
50
|
+
}
|
|
51
|
+
}
|