@latentforce/shift 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/README.md +121 -0
- package/build/index.js +132 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# shift-mcp
|
|
2
|
+
|
|
3
|
+
A minimal utility server built for the Model Context Protocol (MCP). It exposes tools that call the Shift Lite webapp backend for file summary, dependencies, and blast radius (using the project's knowledge graph).
|
|
4
|
+
|
|
5
|
+
## Environment variables
|
|
6
|
+
|
|
7
|
+
- **`SHIFT_PROJECT_ID`** (optional if you pass per call): The default Shift Lite project UUID. If set, all tools use this project unless overridden by the `project_id` parameter in a tool call.
|
|
8
|
+
- Example: `export SHIFT_PROJECT_ID=9af16a19-d073-4134-a0cd-272b0baf912e`
|
|
9
|
+
- **`SHIFT_BACKEND_URL`** (optional): Webapp backend base URL. Default: `http://127.0.0.1:9000`.
|
|
10
|
+
|
|
11
|
+
## Project ID: env vs per-call
|
|
12
|
+
|
|
13
|
+
- **Default:** Set `SHIFT_PROJECT_ID` in the MCP config (or env). All tools use that project.
|
|
14
|
+
- **Override per call:** Each tool (`blast_radius`, `dependencies`, `file_summary`) accepts an optional **`project_id`** parameter. If you pass it, that call uses that project; otherwise the tool uses `SHIFT_PROJECT_ID`. So you can use one MCP for multiple projects by passing `project_id` in the call (e.g. "blast radius for project abc-123").
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### Option 1: Install globally (simple, persistent)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @latentforce/shift
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Verify it works:
|
|
25
|
+
```bash
|
|
26
|
+
shift
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
You should see:
|
|
30
|
+
```bash
|
|
31
|
+
Shift-lite MCP Server running on stdio
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Claude Code setup
|
|
36
|
+
|
|
37
|
+
Claude Code does not auto-discover MCP servers. You must register it manually.
|
|
38
|
+
|
|
39
|
+
### Install the Package (if not already)
|
|
40
|
+
You can use this package without installing it globally by using npx, or install it globally for faster access.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g @latentforce/shift
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Add to Claude Code
|
|
47
|
+
- Replace SHIFT_BACKEND_URL and SHIFT_PROJECT_ID with correct values.
|
|
48
|
+
- SHIFT_PROJECT_ID may be ignored as every tool accepts an optional **`project_id`** parameter.
|
|
49
|
+
For Windows Users
|
|
50
|
+
```bash
|
|
51
|
+
claude mcp add --scope user --transport stdio --env SHIFT_BACKEND_URL=BACKEND_URL --env SHIFT_PROJECT_ID=9af16a19-d073-4134-a0cd-272b0baf912e shift -- cmd /c npx -y @latentforce/shift
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For macOS/Linux Users
|
|
55
|
+
```bash
|
|
56
|
+
claude mcp add --scope user --transport stdio --env SHIFT_BACKEND_URL=BACKEND_URL --env SHIFT_PROJECT_ID=9af16a19-d073-4134-a0cd-272b0baf912e shift -- npx -y @latentforce/shift
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Verify Installation
|
|
60
|
+
```bash
|
|
61
|
+
claude mcp list
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Use It
|
|
65
|
+
Start a Claude Code session:
|
|
66
|
+
```bash
|
|
67
|
+
claude
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Claude Desktop setup
|
|
71
|
+
|
|
72
|
+
Claude Desktop does not auto-discover MCP servers. You must register it manually.
|
|
73
|
+
|
|
74
|
+
### Config file location
|
|
75
|
+
|
|
76
|
+
**macOS (Claude Desktop app):**
|
|
77
|
+
```bash
|
|
78
|
+
~/Library/Application Support/Claude/claude_desktop_config.json
|
|
79
|
+
```
|
|
80
|
+
*(In Claude Desktop: Claude menu → Settings… → Developer → Edit Config)*
|
|
81
|
+
|
|
82
|
+
**Linux (if different):**
|
|
83
|
+
```bash
|
|
84
|
+
~/.config/claude-desktop/claude_desktop_config.json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Windows:**
|
|
88
|
+
```bash
|
|
89
|
+
%APPDATA%\Claude\claude_desktop_config.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Add this (replace `YOUR_PROJECT_UUID` with your Shift Lite project ID):
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"shift": {
|
|
97
|
+
"command": "shift",
|
|
98
|
+
"env": {
|
|
99
|
+
"SHIFT_BACKEND_URL": "BACKEND URL",
|
|
100
|
+
"SHIFT_PROJECT_ID": "YOUR_PROJECT_UUID"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Restart Claude Desktop after saving.
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
Clone and build:
|
|
112
|
+
```bash
|
|
113
|
+
npm install
|
|
114
|
+
npm run build
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Run locally:
|
|
118
|
+
```bash
|
|
119
|
+
node build/index.js
|
|
120
|
+
```
|
|
121
|
+
|
package/build/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
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 { z } from 'zod';
|
|
5
|
+
// Create server instance
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "shift",
|
|
8
|
+
version: "1.0.0",
|
|
9
|
+
});
|
|
10
|
+
const BASE_URL = process.env.SHIFT_BACKEND_URL || "http://127.0.0.1:9000";
|
|
11
|
+
function getProjectIdFromEnv() {
|
|
12
|
+
const projectId = process.env.SHIFT_PROJECT_ID;
|
|
13
|
+
if (!projectId || projectId.trim() === "") {
|
|
14
|
+
throw new Error("SHIFT_PROJECT_ID environment variable is not set. " +
|
|
15
|
+
"Set it to your Shift Lite project UUID, or pass project_id in each tool call.");
|
|
16
|
+
}
|
|
17
|
+
return projectId.trim();
|
|
18
|
+
}
|
|
19
|
+
/** Resolve project_id: use tool arg if provided, else fall back to env. */
|
|
20
|
+
function resolveProjectId(args) {
|
|
21
|
+
const fromArgs = args.project_id?.trim();
|
|
22
|
+
if (fromArgs)
|
|
23
|
+
return fromArgs;
|
|
24
|
+
return getProjectIdFromEnv();
|
|
25
|
+
}
|
|
26
|
+
// helper
|
|
27
|
+
async function callBackendAPI(endpoint, data) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`${BASE_URL}${endpoint}`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify(data),
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const text = await response.text();
|
|
38
|
+
throw new Error(`API call failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`);
|
|
39
|
+
}
|
|
40
|
+
return await response.json();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(`Error calling ${endpoint}:`, error);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Tools:
|
|
48
|
+
// Blast radius
|
|
49
|
+
server.registerTool("blast_radius", {
|
|
50
|
+
description: "Analyzes the blast radius of a file or component - shows what would be affected if this file were modified or deleted",
|
|
51
|
+
inputSchema: z.object({
|
|
52
|
+
file_path: z.string().describe("Path to the file (relative to project root)"),
|
|
53
|
+
project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
|
|
54
|
+
project_path: z.string().optional().describe("Path to the root of the project (optional)"),
|
|
55
|
+
level: z.number().optional().describe("Max depth of blast radius (optional)"),
|
|
56
|
+
})
|
|
57
|
+
}, async (args) => {
|
|
58
|
+
const projectId = resolveProjectId(args);
|
|
59
|
+
const data = await callBackendAPI('/api/v1/mcp/blast-radius', {
|
|
60
|
+
path: args.file_path,
|
|
61
|
+
project_id: projectId,
|
|
62
|
+
...(args.level != null && { level: args.level }),
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: JSON.stringify(data, null, 2)
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
// Dependencies
|
|
74
|
+
server.registerTool("dependencies", {
|
|
75
|
+
description: "Retrieves all dependencies for a given file or component, including direct and transitive dependencies",
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
file_path: z.string().describe("Path to the file"),
|
|
78
|
+
project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
|
|
79
|
+
project_path: z.string().optional().describe("Path to the root of the project (optional)"),
|
|
80
|
+
})
|
|
81
|
+
}, async (args) => {
|
|
82
|
+
const projectId = resolveProjectId(args);
|
|
83
|
+
const data = await callBackendAPI('/api/v1/mcp/dependency', {
|
|
84
|
+
path: args.file_path,
|
|
85
|
+
project_id: projectId,
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: JSON.stringify(data, null, 2)
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
// File summary (maps to what-is-this-file)
|
|
97
|
+
server.registerTool("file_summary", {
|
|
98
|
+
description: "Generates a comprehensive summary of a file including its purpose, exports, imports, and key functions, with optional parent directory context",
|
|
99
|
+
inputSchema: z.object({
|
|
100
|
+
file_path: z.string().describe("Path to the file to summarize"),
|
|
101
|
+
project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
|
|
102
|
+
project_path: z.string().optional().describe("Path to the root of the project (optional)"),
|
|
103
|
+
level: z.number().optional().describe("Number of parent directory levels to include (default 0)"),
|
|
104
|
+
})
|
|
105
|
+
}, async (args) => {
|
|
106
|
+
const projectId = resolveProjectId(args);
|
|
107
|
+
const data = await callBackendAPI('/api/v1/mcp/what-is-this-file', {
|
|
108
|
+
path: args.file_path,
|
|
109
|
+
project_id: projectId,
|
|
110
|
+
level: args.level ?? 0,
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: JSON.stringify(data, null, 2)
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
async function main() {
|
|
122
|
+
const transport = new StdioServerTransport();
|
|
123
|
+
await server.connect(transport);
|
|
124
|
+
console.error("Shift MCP Server running on stdio");
|
|
125
|
+
if (!process.env.SHIFT_PROJECT_ID) {
|
|
126
|
+
console.error("Warning: SHIFT_PROJECT_ID is not set. Pass project_id in each tool call, or set the env var.");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
main().catch((error) => {
|
|
130
|
+
console.error("Fatal error in main():", error);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@latentforce/shift",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "This is latenforce shift MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./build/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"shift": "./build/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"build"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"test": "echo \"No tests yet\""
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"cli",
|
|
25
|
+
"node",
|
|
26
|
+
"typescript"
|
|
27
|
+
],
|
|
28
|
+
"author": "Latentforce",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": ""
|
|
33
|
+
},
|
|
34
|
+
"homepage": "",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
40
|
+
"zod": "^3.25.76"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.0.9",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
}
|
|
46
|
+
}
|