slima-mcp 0.1.6 → 0.1.8
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 +91 -1
- package/dist/index.js +121 -79
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Connect your Slima books to **any MCP-compatible AI tool** - one server, all pla
|
|
|
9
9
|
|
|
10
10
|
## Supported Platforms
|
|
11
11
|
|
|
12
|
+
### Desktop Applications (Local MCP - stdio)
|
|
13
|
+
|
|
12
14
|
| Platform | Status | Notes |
|
|
13
15
|
|----------|--------|-------|
|
|
14
16
|
| Claude Desktop | ✅ | Native MCP support |
|
|
@@ -17,6 +19,13 @@ Connect your Slima books to **any MCP-compatible AI tool** - one server, all pla
|
|
|
17
19
|
| Cursor | ✅ | Native MCP support |
|
|
18
20
|
| VS Code | ✅ | Via MCP extensions |
|
|
19
21
|
|
|
22
|
+
### Web Applications (Remote MCP - HTTP)
|
|
23
|
+
|
|
24
|
+
| Platform | Status | Notes |
|
|
25
|
+
|----------|--------|-------|
|
|
26
|
+
| Claude.ai | ✅ | OAuth login, no token needed |
|
|
27
|
+
| ChatGPT Web | ✅ | OAuth login, no token needed |
|
|
28
|
+
|
|
20
29
|
> MCP became the industry standard in December 2025 when Anthropic, OpenAI, and Block co-founded the Agentic AI Foundation under the Linux Foundation.
|
|
21
30
|
|
|
22
31
|
## Features
|
|
@@ -136,6 +145,48 @@ Get your token from [Slima Settings](https://app.slima.ai/settings/api-tokens).
|
|
|
136
145
|
|
|
137
146
|
After saving the configuration, restart the application to load Slima MCP.
|
|
138
147
|
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Web Applications (Remote MCP)
|
|
151
|
+
|
|
152
|
+
For web-based AI tools like Claude.ai and ChatGPT, use our hosted Remote MCP Server.
|
|
153
|
+
|
|
154
|
+
### One-Click Connection
|
|
155
|
+
|
|
156
|
+
1. In your AI tool, add a new MCP server
|
|
157
|
+
2. Enter the URL: `https://slima-mcp.workers.dev/mcp`
|
|
158
|
+
3. Click "Connect" - you'll be redirected to Slima
|
|
159
|
+
4. Log in (or sign up) and click "Allow"
|
|
160
|
+
5. Done! No token copying needed
|
|
161
|
+
|
|
162
|
+
### How It Works
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
┌─────────────────────────────────────────────────────────┐
|
|
166
|
+
│ Claude.ai / ChatGPT Web │
|
|
167
|
+
│ │
|
|
168
|
+
│ 1. Click "Connect MCP Server" │
|
|
169
|
+
│ 2. Enter: https://slima-mcp.workers.dev/mcp │
|
|
170
|
+
│ ↓ │
|
|
171
|
+
│ 3. Redirect to Slima login │
|
|
172
|
+
│ ↓ │
|
|
173
|
+
│ 4. Approve authorization │
|
|
174
|
+
│ ↓ │
|
|
175
|
+
│ 5. Automatically connected! │
|
|
176
|
+
│ │
|
|
177
|
+
│ No API tokens. No configuration. Just works. │
|
|
178
|
+
└─────────────────────────────────────────────────────────┘
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Security
|
|
182
|
+
|
|
183
|
+
- **OAuth 2.0 + PKCE**: Industry-standard secure authentication
|
|
184
|
+
- **No Secrets Stored**: The server never stores your credentials
|
|
185
|
+
- **Session-Based**: Your token is stored securely in Cloudflare KV
|
|
186
|
+
- **Revocable**: Revoke access anytime from [Slima Settings](https://app.slima.ai/settings/api-tokens)
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
139
190
|
## Available Tools
|
|
140
191
|
|
|
141
192
|
### Book Management
|
|
@@ -213,6 +264,8 @@ Token is stored in `~/.slima/credentials.json` with secure permissions.
|
|
|
213
264
|
|
|
214
265
|
## Development
|
|
215
266
|
|
|
267
|
+
### Local CLI Development
|
|
268
|
+
|
|
216
269
|
```bash
|
|
217
270
|
# Clone the repository
|
|
218
271
|
git clone https://github.com/slima-ai/slima-mcp.git
|
|
@@ -221,7 +274,7 @@ cd slima-mcp
|
|
|
221
274
|
# Install dependencies
|
|
222
275
|
npm install
|
|
223
276
|
|
|
224
|
-
# Build
|
|
277
|
+
# Build CLI
|
|
225
278
|
npm run build
|
|
226
279
|
|
|
227
280
|
# Run tests
|
|
@@ -231,6 +284,43 @@ npm test
|
|
|
231
284
|
npm run dev
|
|
232
285
|
```
|
|
233
286
|
|
|
287
|
+
### Cloudflare Worker Development
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# Build the Worker
|
|
291
|
+
npm run build:worker
|
|
292
|
+
|
|
293
|
+
# Run Worker locally
|
|
294
|
+
npm run dev:worker
|
|
295
|
+
|
|
296
|
+
# Deploy to Cloudflare
|
|
297
|
+
npm run deploy:worker
|
|
298
|
+
|
|
299
|
+
# Deploy to preview environment
|
|
300
|
+
npm run deploy:worker:preview
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Project Structure
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
slima-mcp/
|
|
307
|
+
├── src/
|
|
308
|
+
│ ├── core/ # Shared core modules
|
|
309
|
+
│ │ ├── api/ # Slima API Client
|
|
310
|
+
│ │ ├── tools/ # MCP Tool implementations
|
|
311
|
+
│ │ └── utils/ # Utilities and errors
|
|
312
|
+
│ ├── cli/ # CLI entry point (stdio transport)
|
|
313
|
+
│ │ ├── index.ts # CLI main
|
|
314
|
+
│ │ ├── auth.ts # Authentication commands
|
|
315
|
+
│ │ └── server.ts # MCP Server for CLI
|
|
316
|
+
│ └── worker/ # Cloudflare Worker (HTTP transport)
|
|
317
|
+
│ ├── index.ts # Worker entry point
|
|
318
|
+
│ └── oauth.ts # OAuth 2.0 + PKCE client
|
|
319
|
+
├── wrangler.toml # Cloudflare Worker config
|
|
320
|
+
├── tsup.config.ts # CLI build config
|
|
321
|
+
└── tsup.worker.config.ts # Worker build config
|
|
322
|
+
```
|
|
323
|
+
|
|
234
324
|
## Security
|
|
235
325
|
|
|
236
326
|
- API tokens are stored locally and never shared
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/index.ts
|
|
3
|
+
// src/cli/index.ts
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
|
-
// src/server.ts
|
|
6
|
+
// src/cli/server.ts
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
|
|
9
|
-
// src/utils/errors.ts
|
|
9
|
+
// src/core/utils/errors.ts
|
|
10
10
|
var SlimaApiError = class extends Error {
|
|
11
11
|
constructor(status, code, message) {
|
|
12
12
|
super(message || `API Error: ${status}`);
|
|
@@ -56,52 +56,46 @@ function formatErrorForMcp(error) {
|
|
|
56
56
|
return "An unexpected error occurred.";
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// src/
|
|
60
|
-
var
|
|
61
|
-
|
|
62
|
-
info: (message, ...args) => {
|
|
63
|
-
console.error(`[INFO] ${message}`, ...args);
|
|
59
|
+
// src/core/api/client.ts
|
|
60
|
+
var defaultLogger = {
|
|
61
|
+
debug: () => {
|
|
64
62
|
},
|
|
65
|
-
|
|
66
|
-
console.error(`[WARN] ${message}`, ...args);
|
|
63
|
+
info: () => {
|
|
67
64
|
},
|
|
68
|
-
|
|
69
|
-
if (error instanceof Error) {
|
|
70
|
-
console.error(`[ERROR] ${message}:`, error.message);
|
|
71
|
-
if (isDebug && error.stack) {
|
|
72
|
-
console.error(error.stack);
|
|
73
|
-
}
|
|
74
|
-
} else if (error !== void 0) {
|
|
75
|
-
console.error(`[ERROR] ${message}:`, error);
|
|
76
|
-
} else {
|
|
77
|
-
console.error(`[ERROR] ${message}`);
|
|
78
|
-
}
|
|
65
|
+
warn: () => {
|
|
79
66
|
},
|
|
80
|
-
|
|
81
|
-
if (isDebug) {
|
|
82
|
-
console.error(`[DEBUG] ${message}`, ...args);
|
|
83
|
-
}
|
|
67
|
+
error: () => {
|
|
84
68
|
}
|
|
85
69
|
};
|
|
86
|
-
|
|
87
|
-
|
|
70
|
+
function isLegacyConfig(config) {
|
|
71
|
+
return "token" in config && typeof config.token === "string";
|
|
72
|
+
}
|
|
88
73
|
var SlimaApiClient = class {
|
|
89
74
|
baseUrl;
|
|
90
|
-
|
|
75
|
+
getToken;
|
|
76
|
+
logger;
|
|
91
77
|
constructor(config) {
|
|
92
78
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
93
|
-
|
|
79
|
+
if (isLegacyConfig(config)) {
|
|
80
|
+
const staticToken = config.token;
|
|
81
|
+
this.getToken = async () => staticToken;
|
|
82
|
+
this.logger = defaultLogger;
|
|
83
|
+
} else {
|
|
84
|
+
this.getToken = config.getToken;
|
|
85
|
+
this.logger = config.logger || defaultLogger;
|
|
86
|
+
}
|
|
94
87
|
}
|
|
95
88
|
/**
|
|
96
|
-
*
|
|
89
|
+
* Send API request
|
|
97
90
|
*/
|
|
98
91
|
async request(method, path, body) {
|
|
99
92
|
const url = `${this.baseUrl}${path}`;
|
|
100
|
-
|
|
93
|
+
const token = await this.getToken();
|
|
94
|
+
this.logger.debug(`${method} ${path}`);
|
|
101
95
|
const response = await fetch(url, {
|
|
102
96
|
method,
|
|
103
97
|
headers: {
|
|
104
|
-
Authorization: `Bearer ${
|
|
98
|
+
Authorization: `Bearer ${token}`,
|
|
105
99
|
"Content-Type": "application/json",
|
|
106
100
|
"User-Agent": "slima-mcp-server/0.1.0"
|
|
107
101
|
},
|
|
@@ -114,7 +108,7 @@ var SlimaApiClient = class {
|
|
|
114
108
|
return json.data;
|
|
115
109
|
}
|
|
116
110
|
/**
|
|
117
|
-
*
|
|
111
|
+
* Handle error response
|
|
118
112
|
*/
|
|
119
113
|
async handleErrorResponse(response) {
|
|
120
114
|
let errorData;
|
|
@@ -135,21 +129,21 @@ var SlimaApiClient = class {
|
|
|
135
129
|
throw new SlimaApiError(response.status, code, message);
|
|
136
130
|
}
|
|
137
131
|
}
|
|
138
|
-
// ===
|
|
132
|
+
// === Book Operations ===
|
|
139
133
|
/**
|
|
140
|
-
*
|
|
134
|
+
* List all books
|
|
141
135
|
*/
|
|
142
136
|
async listBooks() {
|
|
143
137
|
return this.request("GET", "/api/v1/books");
|
|
144
138
|
}
|
|
145
139
|
/**
|
|
146
|
-
*
|
|
140
|
+
* Get book details
|
|
147
141
|
*/
|
|
148
142
|
async getBook(token) {
|
|
149
143
|
return this.request("GET", `/api/v1/books/${token}`);
|
|
150
144
|
}
|
|
151
145
|
/**
|
|
152
|
-
*
|
|
146
|
+
* Create a new book
|
|
153
147
|
*/
|
|
154
148
|
async createBook(params) {
|
|
155
149
|
return this.request("POST", "/api/v1/books", {
|
|
@@ -160,10 +154,9 @@ var SlimaApiClient = class {
|
|
|
160
154
|
}
|
|
161
155
|
});
|
|
162
156
|
}
|
|
163
|
-
// ===
|
|
157
|
+
// === Version Control ===
|
|
164
158
|
/**
|
|
165
|
-
*
|
|
166
|
-
* 注意:API 回傳 { commits: [...] },需要解構
|
|
159
|
+
* List commits
|
|
167
160
|
*/
|
|
168
161
|
async listCommits(bookToken, limit = 10) {
|
|
169
162
|
const response = await this.request(
|
|
@@ -173,7 +166,7 @@ var SlimaApiClient = class {
|
|
|
173
166
|
return response.commits;
|
|
174
167
|
}
|
|
175
168
|
/**
|
|
176
|
-
*
|
|
169
|
+
* Get commit details
|
|
177
170
|
*/
|
|
178
171
|
async getCommit(bookToken, commitToken) {
|
|
179
172
|
return this.request(
|
|
@@ -182,14 +175,13 @@ var SlimaApiClient = class {
|
|
|
182
175
|
);
|
|
183
176
|
}
|
|
184
177
|
/**
|
|
185
|
-
*
|
|
178
|
+
* List branches
|
|
186
179
|
*/
|
|
187
180
|
async listBranches(bookToken) {
|
|
188
181
|
return this.request("GET", `/api/v1/books/${bookToken}/branches`);
|
|
189
182
|
}
|
|
190
183
|
/**
|
|
191
|
-
*
|
|
192
|
-
* 注意:API 回傳 { blobs: [...], notFound: [...], truncated: ... }
|
|
184
|
+
* Download blobs
|
|
193
185
|
*/
|
|
194
186
|
async downloadBlobs(bookToken, hashes) {
|
|
195
187
|
const response = await this.request(
|
|
@@ -199,23 +191,23 @@ var SlimaApiClient = class {
|
|
|
199
191
|
);
|
|
200
192
|
return response.blobs;
|
|
201
193
|
}
|
|
202
|
-
// === Persona
|
|
194
|
+
// === Persona ===
|
|
203
195
|
/**
|
|
204
|
-
*
|
|
196
|
+
* List personas
|
|
205
197
|
*/
|
|
206
198
|
async listPersonas(genre) {
|
|
207
199
|
const params = genre ? `?genre=${encodeURIComponent(genre)}` : "";
|
|
208
200
|
return this.request("GET", `/api/v1/personas${params}`);
|
|
209
201
|
}
|
|
210
202
|
/**
|
|
211
|
-
*
|
|
203
|
+
* Get persona details
|
|
212
204
|
*/
|
|
213
205
|
async getPersona(token) {
|
|
214
206
|
return this.request("GET", `/api/v1/personas/${token}`);
|
|
215
207
|
}
|
|
216
|
-
// === Reader Test
|
|
208
|
+
// === Reader Test ===
|
|
217
209
|
/**
|
|
218
|
-
*
|
|
210
|
+
* Create reader test
|
|
219
211
|
*/
|
|
220
212
|
async createReaderTest(bookToken, params) {
|
|
221
213
|
return this.request(
|
|
@@ -234,7 +226,7 @@ var SlimaApiClient = class {
|
|
|
234
226
|
);
|
|
235
227
|
}
|
|
236
228
|
/**
|
|
237
|
-
*
|
|
229
|
+
* Get reader test progress
|
|
238
230
|
*/
|
|
239
231
|
async getReaderTestProgress(bookToken, testToken) {
|
|
240
232
|
return this.request(
|
|
@@ -243,7 +235,7 @@ var SlimaApiClient = class {
|
|
|
243
235
|
);
|
|
244
236
|
}
|
|
245
237
|
/**
|
|
246
|
-
*
|
|
238
|
+
* Get reader test result
|
|
247
239
|
*/
|
|
248
240
|
async getReaderTest(bookToken, testToken) {
|
|
249
241
|
return this.request(
|
|
@@ -253,7 +245,7 @@ var SlimaApiClient = class {
|
|
|
253
245
|
}
|
|
254
246
|
// === MCP File Operations ===
|
|
255
247
|
/**
|
|
256
|
-
*
|
|
248
|
+
* Read file content
|
|
257
249
|
*/
|
|
258
250
|
async readFile(bookToken, path) {
|
|
259
251
|
return this.request(
|
|
@@ -263,7 +255,7 @@ var SlimaApiClient = class {
|
|
|
263
255
|
);
|
|
264
256
|
}
|
|
265
257
|
/**
|
|
266
|
-
*
|
|
258
|
+
* Create file
|
|
267
259
|
*/
|
|
268
260
|
async createFile(bookToken, params) {
|
|
269
261
|
return this.request(
|
|
@@ -278,7 +270,7 @@ var SlimaApiClient = class {
|
|
|
278
270
|
);
|
|
279
271
|
}
|
|
280
272
|
/**
|
|
281
|
-
*
|
|
273
|
+
* Update (overwrite) file content
|
|
282
274
|
*/
|
|
283
275
|
async updateFile(bookToken, params) {
|
|
284
276
|
return this.request(
|
|
@@ -292,7 +284,7 @@ var SlimaApiClient = class {
|
|
|
292
284
|
);
|
|
293
285
|
}
|
|
294
286
|
/**
|
|
295
|
-
*
|
|
287
|
+
* Delete file
|
|
296
288
|
*/
|
|
297
289
|
async deleteFile(bookToken, params) {
|
|
298
290
|
return this.request(
|
|
@@ -305,7 +297,7 @@ var SlimaApiClient = class {
|
|
|
305
297
|
);
|
|
306
298
|
}
|
|
307
299
|
/**
|
|
308
|
-
*
|
|
300
|
+
* Append content to file
|
|
309
301
|
*/
|
|
310
302
|
async appendToFile(bookToken, params) {
|
|
311
303
|
return this.request(
|
|
@@ -319,7 +311,7 @@ var SlimaApiClient = class {
|
|
|
319
311
|
);
|
|
320
312
|
}
|
|
321
313
|
/**
|
|
322
|
-
*
|
|
314
|
+
* Search file content
|
|
323
315
|
*/
|
|
324
316
|
async searchFiles(bookToken, params) {
|
|
325
317
|
return this.request(
|
|
@@ -334,7 +326,7 @@ var SlimaApiClient = class {
|
|
|
334
326
|
}
|
|
335
327
|
};
|
|
336
328
|
|
|
337
|
-
// src/tools/books.ts
|
|
329
|
+
// src/core/tools/books.ts
|
|
338
330
|
import { z } from "zod";
|
|
339
331
|
function registerBookTools(server, client) {
|
|
340
332
|
server.tool(
|
|
@@ -536,7 +528,7 @@ function formatFileTree(files, indent = 0) {
|
|
|
536
528
|
}).join("\n");
|
|
537
529
|
}
|
|
538
530
|
|
|
539
|
-
// src/tools/content.ts
|
|
531
|
+
// src/core/tools/content.ts
|
|
540
532
|
import { z as z2 } from "zod";
|
|
541
533
|
function registerContentTools(server, client) {
|
|
542
534
|
server.tool(
|
|
@@ -691,9 +683,19 @@ function listAllFiles(files, prefix = "") {
|
|
|
691
683
|
return result;
|
|
692
684
|
}
|
|
693
685
|
|
|
694
|
-
// src/tools/beta-reader.ts
|
|
686
|
+
// src/core/tools/beta-reader.ts
|
|
695
687
|
import { z as z3 } from "zod";
|
|
696
|
-
|
|
688
|
+
var defaultLogger2 = {
|
|
689
|
+
debug: () => {
|
|
690
|
+
},
|
|
691
|
+
info: () => {
|
|
692
|
+
},
|
|
693
|
+
warn: () => {
|
|
694
|
+
},
|
|
695
|
+
error: () => {
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
function registerBetaReaderTools(server, client, logger2 = defaultLogger2) {
|
|
697
699
|
server.tool(
|
|
698
700
|
"list_personas",
|
|
699
701
|
"List available virtual reader personas for beta reading feedback",
|
|
@@ -802,7 +804,7 @@ ${formatted}
|
|
|
802
804
|
};
|
|
803
805
|
}
|
|
804
806
|
const content = blobs[0].content;
|
|
805
|
-
|
|
807
|
+
logger2.info(`Creating reader test for ${file_path} with persona ${persona_token}`);
|
|
806
808
|
const test = await client.createReaderTest(book_token, {
|
|
807
809
|
reportType: "chapter",
|
|
808
810
|
personaTokens: [persona_token],
|
|
@@ -812,7 +814,8 @@ ${formatted}
|
|
|
812
814
|
});
|
|
813
815
|
const result = await pollForCompletion(
|
|
814
816
|
() => client.getReaderTestProgress(book_token, test.token),
|
|
815
|
-
9e4
|
|
817
|
+
9e4,
|
|
818
|
+
logger2
|
|
816
819
|
);
|
|
817
820
|
if (result.status === "failed") {
|
|
818
821
|
return {
|
|
@@ -851,12 +854,12 @@ Test ID: ${test.token}`
|
|
|
851
854
|
}
|
|
852
855
|
);
|
|
853
856
|
}
|
|
854
|
-
async function pollForCompletion(checkFn, timeoutMs) {
|
|
857
|
+
async function pollForCompletion(checkFn, timeoutMs, logger2) {
|
|
855
858
|
const startTime = Date.now();
|
|
856
859
|
const pollInterval = 3e3;
|
|
857
860
|
while (Date.now() - startTime < timeoutMs) {
|
|
858
861
|
const result = await checkFn();
|
|
859
|
-
|
|
862
|
+
logger2.debug(`Poll status: ${result.status}, progress: ${result.progress || "N/A"}`);
|
|
860
863
|
if (result.status === "completed" || result.status === "failed") {
|
|
861
864
|
return result;
|
|
862
865
|
}
|
|
@@ -1095,7 +1098,7 @@ function searchByName2(files, name) {
|
|
|
1095
1098
|
return null;
|
|
1096
1099
|
}
|
|
1097
1100
|
|
|
1098
|
-
// src/tools/files.ts
|
|
1101
|
+
// src/core/tools/files.ts
|
|
1099
1102
|
import { z as z4 } from "zod";
|
|
1100
1103
|
function registerFileTools(server, client) {
|
|
1101
1104
|
server.tool(
|
|
@@ -1397,26 +1400,55 @@ Message: ${result.commit.name || result.commit.message}`
|
|
|
1397
1400
|
);
|
|
1398
1401
|
}
|
|
1399
1402
|
|
|
1400
|
-
// src/
|
|
1403
|
+
// src/core/utils/logger.ts
|
|
1404
|
+
var isDebug = process.env.DEBUG === "true" || process.env.DEBUG === "1";
|
|
1405
|
+
var logger = {
|
|
1406
|
+
info: (message, ...args) => {
|
|
1407
|
+
console.error(`[INFO] ${message}`, ...args);
|
|
1408
|
+
},
|
|
1409
|
+
warn: (message, ...args) => {
|
|
1410
|
+
console.error(`[WARN] ${message}`, ...args);
|
|
1411
|
+
},
|
|
1412
|
+
error: (message, error) => {
|
|
1413
|
+
if (error instanceof Error) {
|
|
1414
|
+
console.error(`[ERROR] ${message}:`, error.message);
|
|
1415
|
+
if (isDebug && error.stack) {
|
|
1416
|
+
console.error(error.stack);
|
|
1417
|
+
}
|
|
1418
|
+
} else if (error !== void 0) {
|
|
1419
|
+
console.error(`[ERROR] ${message}:`, error);
|
|
1420
|
+
} else {
|
|
1421
|
+
console.error(`[ERROR] ${message}`);
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1424
|
+
debug: (message, ...args) => {
|
|
1425
|
+
if (isDebug) {
|
|
1426
|
+
console.error(`[DEBUG] ${message}`, ...args);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
|
|
1431
|
+
// src/cli/server.ts
|
|
1401
1432
|
function createSlimaServer(config) {
|
|
1402
1433
|
const client = new SlimaApiClient({
|
|
1403
|
-
|
|
1404
|
-
|
|
1434
|
+
baseUrl: config.baseUrl || "https://api.slima.ai",
|
|
1435
|
+
getToken: async () => config.apiToken,
|
|
1436
|
+
logger
|
|
1405
1437
|
});
|
|
1406
1438
|
const server = new McpServer({
|
|
1407
1439
|
name: "slima",
|
|
1408
|
-
version: "0.1.
|
|
1440
|
+
version: "0.1.8"
|
|
1409
1441
|
});
|
|
1410
1442
|
registerBookTools(server, client);
|
|
1411
1443
|
registerContentTools(server, client);
|
|
1412
|
-
registerBetaReaderTools(server, client);
|
|
1444
|
+
registerBetaReaderTools(server, client, logger);
|
|
1413
1445
|
registerFileTools(server, client);
|
|
1414
1446
|
logger.info("Slima MCP Server initialized");
|
|
1415
1447
|
logger.info(`API endpoint: ${config.baseUrl || "https://api.slima.ai"}`);
|
|
1416
1448
|
return server;
|
|
1417
1449
|
}
|
|
1418
1450
|
|
|
1419
|
-
// src/
|
|
1451
|
+
// src/cli/token-storage.ts
|
|
1420
1452
|
import { promises as fs } from "fs";
|
|
1421
1453
|
import { homedir } from "os";
|
|
1422
1454
|
import { join } from "path";
|
|
@@ -1748,14 +1780,19 @@ function getErrorHtml(message) {
|
|
|
1748
1780
|
}
|
|
1749
1781
|
async function runAuth(options = {}) {
|
|
1750
1782
|
const baseUrl = options.baseUrl || process.env.SLIMA_API_URL || DEFAULT_API_URL;
|
|
1783
|
+
const forceAuth = options.force || false;
|
|
1751
1784
|
console.log("\n\u{1F510} Slima MCP Authentication\n");
|
|
1752
1785
|
const existingCredentials = await loadToken();
|
|
1753
|
-
if (existingCredentials) {
|
|
1786
|
+
if (existingCredentials && !forceAuth) {
|
|
1754
1787
|
console.log("You are already authenticated.");
|
|
1755
1788
|
console.log(`Token stored at: ${getCredentialsFilePath()}`);
|
|
1756
|
-
console.log("\nTo re-authenticate,
|
|
1789
|
+
console.log("\nTo re-authenticate with a different account, run: slima-mcp auth --force\n");
|
|
1757
1790
|
return;
|
|
1758
1791
|
}
|
|
1792
|
+
if (existingCredentials && forceAuth) {
|
|
1793
|
+
await deleteToken();
|
|
1794
|
+
console.log("Clearing existing credentials for re-authentication...\n");
|
|
1795
|
+
}
|
|
1759
1796
|
const state = generateState();
|
|
1760
1797
|
const port = await findAvailablePort();
|
|
1761
1798
|
console.log("Starting local server for authentication callback...");
|
|
@@ -1804,7 +1841,8 @@ async function runAuth(options = {}) {
|
|
|
1804
1841
|
reject(new Error("Authentication timed out. Please try again."));
|
|
1805
1842
|
}, 3e4);
|
|
1806
1843
|
});
|
|
1807
|
-
const
|
|
1844
|
+
const promptParam = forceAuth ? "&prompt=login" : "";
|
|
1845
|
+
const authUrl = `${baseUrl}/api/v1/auth/cli?state=${state}&callback_port=${port}${promptParam}`;
|
|
1808
1846
|
console.log("\nOpening browser for authentication...");
|
|
1809
1847
|
await openBrowser(authUrl);
|
|
1810
1848
|
console.log("\nWaiting for authentication...");
|
|
@@ -1850,8 +1888,8 @@ async function runStatus() {
|
|
|
1850
1888
|
`);
|
|
1851
1889
|
}
|
|
1852
1890
|
|
|
1853
|
-
// src/index.ts
|
|
1854
|
-
var VERSION = "0.1.
|
|
1891
|
+
// src/cli/index.ts
|
|
1892
|
+
var VERSION = "0.1.8";
|
|
1855
1893
|
function showHelp() {
|
|
1856
1894
|
console.log(`
|
|
1857
1895
|
Slima MCP v${VERSION}
|
|
@@ -1863,6 +1901,7 @@ USAGE:
|
|
|
1863
1901
|
COMMANDS:
|
|
1864
1902
|
(none) Start the MCP server
|
|
1865
1903
|
auth Authenticate with Slima (opens browser)
|
|
1904
|
+
auth --force Re-authenticate (switch account)
|
|
1866
1905
|
logout Remove saved credentials
|
|
1867
1906
|
status Show authentication status
|
|
1868
1907
|
--help Show this help message
|
|
@@ -1877,6 +1916,7 @@ AUTHENTICATION:
|
|
|
1877
1916
|
|
|
1878
1917
|
EXAMPLES:
|
|
1879
1918
|
slima-mcp auth # Authenticate with browser
|
|
1919
|
+
slima-mcp auth --force # Re-authenticate (switch account)
|
|
1880
1920
|
slima-mcp status # Check auth status
|
|
1881
1921
|
SLIMA_API_TOKEN=xxx slima-mcp # Use env var
|
|
1882
1922
|
|
|
@@ -1947,9 +1987,11 @@ async function main() {
|
|
|
1947
1987
|
const args = process.argv.slice(2);
|
|
1948
1988
|
const command = args[0];
|
|
1949
1989
|
switch (command) {
|
|
1950
|
-
case "auth":
|
|
1951
|
-
|
|
1990
|
+
case "auth": {
|
|
1991
|
+
const forceFlag = args.includes("--force") || args.includes("-f");
|
|
1992
|
+
await runAuth({ force: forceFlag });
|
|
1952
1993
|
break;
|
|
1994
|
+
}
|
|
1953
1995
|
case "logout":
|
|
1954
1996
|
await runLogout();
|
|
1955
1997
|
break;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/utils/errors.ts","../src/utils/logger.ts","../src/api/client.ts","../src/tools/books.ts","../src/tools/content.ts","../src/tools/beta-reader.ts","../src/tools/files.ts","../src/utils/token-storage.ts","../src/cli/auth.ts"],"sourcesContent":["/**\n * Slima MCP Server - CLI Entry Point\n *\n * Usage:\n * slima-mcp - Start MCP server (requires SLIMA_API_TOKEN or prior auth)\n * slima-mcp auth - Authenticate with Slima (opens browser)\n * slima-mcp logout - Remove saved credentials\n * slima-mcp status - Show authentication status\n * slima-mcp --help - Show help\n */\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createSlimaServer } from './server.js';\nimport { logger } from './utils/logger.js';\nimport { loadToken, getCredentialsFilePath } from './utils/token-storage.js';\nimport { runAuth, runLogout, runStatus } from './cli/auth.js';\n\ndeclare const __VERSION__: string;\nconst VERSION = __VERSION__;\n\nfunction showHelp(): void {\n console.log(`\nSlima MCP v${VERSION}\nMCP Server for Slima - AI Writing IDE for Novel Authors\n\nUSAGE:\n slima-mcp [command]\n\nCOMMANDS:\n (none) Start the MCP server\n auth Authenticate with Slima (opens browser)\n logout Remove saved credentials\n status Show authentication status\n --help Show this help message\n --version Show version\n\nAUTHENTICATION:\n 1. Run: slima-mcp auth\n 2. Complete authentication in browser\n 3. Token is automatically saved\n\n Or set SLIMA_API_TOKEN environment variable.\n\nEXAMPLES:\n slima-mcp auth # Authenticate with browser\n slima-mcp status # Check auth status\n SLIMA_API_TOKEN=xxx slima-mcp # Use env var\n\nCONFIGURATION (for AI tools):\n Claude Desktop, Cursor, etc.:\n {\n \"mcpServers\": {\n \"slima\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"slima-mcp\"]\n }\n }\n }\n\nMORE INFO:\n https://github.com/slima-ai/slima-mcp\n`);\n}\n\nfunction showVersion(): void {\n console.log(`slima-mcp v${VERSION}`);\n}\n\nasync function startServer(): Promise<void> {\n // 優先使用環境變數,否則使用儲存的 token\n let apiToken = process.env.SLIMA_API_TOKEN;\n let baseUrl = process.env.SLIMA_API_URL;\n\n if (!apiToken) {\n const credentials = await loadToken();\n if (credentials) {\n apiToken = credentials.apiToken;\n baseUrl = baseUrl || credentials.baseUrl;\n }\n }\n\n if (!apiToken) {\n logger.error('Not authenticated and SLIMA_API_TOKEN not set');\n logger.error('');\n logger.error('To authenticate, run:');\n logger.error(' slima-mcp auth');\n logger.error('');\n logger.error('Or set environment variable:');\n logger.error(' SLIMA_API_TOKEN=your_token slima-mcp');\n logger.error('');\n logger.error('Get your API token from: https://app.slima.ai/settings/api-tokens');\n process.exit(1);\n }\n\n try {\n const server = createSlimaServer({\n apiToken,\n baseUrl,\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n logger.info('Slima MCP Server running on stdio');\n\n // 優雅關閉\n process.on('SIGINT', async () => {\n logger.info('Shutting down...');\n await server.close();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n logger.info('Shutting down...');\n await server.close();\n process.exit(0);\n });\n } catch (error) {\n logger.error('Failed to start server', error);\n process.exit(1);\n }\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0];\n\n switch (command) {\n case 'auth':\n await runAuth();\n break;\n\n case 'logout':\n await runLogout();\n break;\n\n case 'status':\n await runStatus();\n break;\n\n case '--help':\n case '-h':\n case 'help':\n showHelp();\n break;\n\n case '--version':\n case '-v':\n showVersion();\n break;\n\n case undefined:\n // 預設:啟動 MCP server\n await startServer();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n console.error('Run `slima-mcp --help` for usage.');\n process.exit(1);\n }\n}\n\nmain();\n","/**\n * Slima MCP Server\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SlimaApiClient } from './api/client.js';\nimport { registerBookTools } from './tools/books.js';\nimport { registerContentTools } from './tools/content.js';\nimport { registerBetaReaderTools } from './tools/beta-reader.js';\nimport { registerFileTools } from './tools/files.js';\nimport { logger } from './utils/logger.js';\n\ndeclare const __VERSION__: string;\n\nexport interface ServerConfig {\n apiToken: string;\n baseUrl?: string;\n}\n\n/**\n * 建立 Slima MCP Server\n */\nexport function createSlimaServer(config: ServerConfig): McpServer {\n const client = new SlimaApiClient({\n token: config.apiToken,\n baseUrl: config.baseUrl || 'https://api.slima.ai',\n });\n\n const server = new McpServer({\n name: 'slima',\n version: __VERSION__,\n });\n\n // 註冊工具\n registerBookTools(server, client);\n registerContentTools(server, client);\n registerBetaReaderTools(server, client);\n registerFileTools(server, client);\n\n logger.info('Slima MCP Server initialized');\n logger.info(`API endpoint: ${config.baseUrl || 'https://api.slima.ai'}`);\n\n return server;\n}\n","/**\n * 錯誤處理工具\n */\n\nexport class SlimaApiError extends Error {\n constructor(\n public status: number,\n public code?: string,\n message?: string\n ) {\n super(message || `API Error: ${status}`);\n this.name = 'SlimaApiError';\n }\n}\n\nexport class AuthenticationError extends SlimaApiError {\n constructor(message = 'Invalid or expired API token') {\n super(401, 'UNAUTHORIZED', message);\n this.name = 'AuthenticationError';\n }\n}\n\nexport class NotFoundError extends SlimaApiError {\n constructor(resource = 'Resource') {\n super(404, 'NOT_FOUND', `${resource} not found`);\n this.name = 'NotFoundError';\n }\n}\n\nexport class InsufficientCreditsError extends SlimaApiError {\n constructor() {\n super(402, 'INSUFFICIENT_CREDITS', 'Insufficient credits');\n this.name = 'InsufficientCreditsError';\n }\n}\n\n/**\n * 格式化錯誤訊息供 MCP 回傳\n */\nexport function formatErrorForMcp(error: unknown): string {\n if (error instanceof SlimaApiError) {\n switch (error.code) {\n case 'NOT_FOUND':\n return 'Resource not found. Please check the token.';\n case 'UNAUTHORIZED':\n return 'Invalid API token. Please check your configuration.';\n case 'INSUFFICIENT_CREDITS':\n return 'Insufficient credits. Please top up your account.';\n case 'TOKEN_EXPIRED':\n return 'API token has expired. Please generate a new one.';\n case 'TOKEN_REVOKED':\n return 'API token has been revoked. Please generate a new one.';\n default:\n return error.message;\n }\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return 'An unexpected error occurred.';\n}\n","/**\n * Logger 工具\n *\n * 重要:MCP STDIO 模式下,stdout 是協議通道,\n * 所有日誌必須輸出到 stderr\n */\n\nconst isDebug = process.env.DEBUG === 'true' || process.env.DEBUG === '1';\n\nexport const logger = {\n info: (message: string, ...args: unknown[]) => {\n console.error(`[INFO] ${message}`, ...args);\n },\n\n warn: (message: string, ...args: unknown[]) => {\n console.error(`[WARN] ${message}`, ...args);\n },\n\n error: (message: string, error?: unknown) => {\n if (error instanceof Error) {\n console.error(`[ERROR] ${message}:`, error.message);\n if (isDebug && error.stack) {\n console.error(error.stack);\n }\n } else if (error !== undefined) {\n console.error(`[ERROR] ${message}:`, error);\n } else {\n console.error(`[ERROR] ${message}`);\n }\n },\n\n debug: (message: string, ...args: unknown[]) => {\n if (isDebug) {\n console.error(`[DEBUG] ${message}`, ...args);\n }\n },\n};\n","/**\n * Slima API Client\n */\n\nimport {\n type Book,\n type Commit,\n type Branch,\n type Blob,\n type Persona,\n type ReaderTest,\n type ReaderTestProgress,\n type CreateReaderTestParams,\n type ApiResponse,\n type ApiError,\n type CommitsListResponse,\n type BlobsDownloadResponse,\n type McpFileReadResponse,\n type McpFileCreateResponse,\n type McpFileUpdateResponse,\n type McpFileDeleteResponse,\n type McpFileAppendResponse,\n type McpSearchResponse,\n} from './types.js';\nimport {\n SlimaApiError,\n AuthenticationError,\n NotFoundError,\n InsufficientCreditsError,\n} from '../utils/errors.js';\nimport { logger } from '../utils/logger.js';\n\nexport interface SlimaConfig {\n baseUrl: string;\n token: string;\n}\n\nexport class SlimaApiClient {\n private readonly baseUrl: string;\n private readonly token: string;\n\n constructor(config: SlimaConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, ''); // 移除尾部斜線\n this.token = config.token;\n }\n\n /**\n * 發送 API 請求\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n logger.debug(`${method} ${path}`);\n\n const response = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'slima-mcp-server/0.1.0',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n const json = (await response.json()) as ApiResponse<T>;\n return json.data;\n }\n\n /**\n * 處理錯誤回應\n */\n private async handleErrorResponse(response: Response): Promise<never> {\n let errorData: ApiError | undefined;\n\n try {\n errorData = (await response.json()) as ApiError;\n } catch {\n // 無法解析 JSON\n }\n\n const code = errorData?.error?.code;\n const message = errorData?.error?.message;\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message);\n case 404:\n throw new NotFoundError(message);\n case 402:\n throw new InsufficientCreditsError();\n default:\n throw new SlimaApiError(response.status, code, message);\n }\n }\n\n // === 書籍相關 ===\n\n /**\n * 列出所有書籍\n */\n async listBooks(): Promise<Book[]> {\n return this.request<Book[]>('GET', '/api/v1/books');\n }\n\n /**\n * 取得書籍詳情\n */\n async getBook(token: string): Promise<Book> {\n return this.request<Book>('GET', `/api/v1/books/${token}`);\n }\n\n /**\n * 建立新書籍\n */\n async createBook(params: {\n title: string;\n authorName?: string;\n description?: string;\n }): Promise<Book> {\n return this.request<Book>('POST', '/api/v1/books', {\n book: {\n title: params.title,\n author_name: params.authorName,\n description: params.description,\n },\n });\n }\n\n // === 版本控制相關 ===\n\n /**\n * 列出 Commits\n * 注意:API 回傳 { commits: [...] },需要解構\n */\n async listCommits(bookToken: string, limit = 10): Promise<Commit[]> {\n const response = await this.request<CommitsListResponse>(\n 'GET',\n `/api/v1/books/${bookToken}/commits?limit=${limit}`\n );\n return response.commits;\n }\n\n /**\n * 取得 Commit 詳情\n */\n async getCommit(bookToken: string, commitToken: string): Promise<Commit> {\n return this.request<Commit>(\n 'GET',\n `/api/v1/books/${bookToken}/commits/${commitToken}`\n );\n }\n\n /**\n * 列出分支\n */\n async listBranches(bookToken: string): Promise<Branch[]> {\n return this.request<Branch[]>('GET', `/api/v1/books/${bookToken}/branches`);\n }\n\n /**\n * 下載 Blobs\n * 注意:API 回傳 { blobs: [...], notFound: [...], truncated: ... }\n */\n async downloadBlobs(bookToken: string, hashes: string[]): Promise<Blob[]> {\n const response = await this.request<BlobsDownloadResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/blobs/download`,\n { hashes }\n );\n return response.blobs;\n }\n\n // === Persona 相關 ===\n\n /**\n * 列出所有 Personas\n */\n async listPersonas(genre?: string): Promise<Persona[]> {\n const params = genre ? `?genre=${encodeURIComponent(genre)}` : '';\n return this.request<Persona[]>('GET', `/api/v1/personas${params}`);\n }\n\n /**\n * 取得 Persona 詳情\n */\n async getPersona(token: string): Promise<Persona> {\n return this.request<Persona>('GET', `/api/v1/personas/${token}`);\n }\n\n // === Reader Test 相關 ===\n\n /**\n * 建立 Reader Test\n */\n async createReaderTest(\n bookToken: string,\n params: CreateReaderTestParams\n ): Promise<ReaderTest> {\n return this.request<ReaderTest>(\n 'POST',\n `/api/v1/books/${bookToken}/reader_tests`,\n {\n reader_test: {\n report_type: params.reportType || 'chapter',\n persona_tokens: params.personaTokens,\n commit_token: params.commitToken,\n model: params.model,\n scope_config: params.scopeConfig,\n },\n content: params.content,\n }\n );\n }\n\n /**\n * 取得 Reader Test 進度\n */\n async getReaderTestProgress(\n bookToken: string,\n testToken: string\n ): Promise<ReaderTestProgress> {\n return this.request<ReaderTestProgress>(\n 'GET',\n `/api/v1/books/${bookToken}/reader_tests/${testToken}/progress`\n );\n }\n\n /**\n * 取得 Reader Test 結果\n */\n async getReaderTest(bookToken: string, testToken: string): Promise<ReaderTest> {\n return this.request<ReaderTest>(\n 'GET',\n `/api/v1/books/${bookToken}/reader_tests/${testToken}`\n );\n }\n\n // === MCP File Operations ===\n\n /**\n * 讀取檔案內容\n */\n async readFile(bookToken: string, path: string): Promise<McpFileReadResponse> {\n return this.request<McpFileReadResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/read`,\n { path }\n );\n }\n\n /**\n * 建立新檔案\n */\n async createFile(\n bookToken: string,\n params: { path: string; content?: string; parentPath?: string; commitMessage?: string }\n ): Promise<McpFileCreateResponse> {\n return this.request<McpFileCreateResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/create`,\n {\n path: params.path,\n content: params.content || '',\n parent_path: params.parentPath,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * 更新(覆寫)檔案內容\n */\n async updateFile(\n bookToken: string,\n params: { path: string; content: string; commitMessage?: string }\n ): Promise<McpFileUpdateResponse> {\n return this.request<McpFileUpdateResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/update`,\n {\n path: params.path,\n content: params.content,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * 刪除檔案\n */\n async deleteFile(\n bookToken: string,\n params: { path: string; commitMessage?: string }\n ): Promise<McpFileDeleteResponse> {\n return this.request<McpFileDeleteResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/delete`,\n {\n path: params.path,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * 附加內容到檔案\n */\n async appendToFile(\n bookToken: string,\n params: { path: string; content: string; commitMessage?: string }\n ): Promise<McpFileAppendResponse> {\n return this.request<McpFileAppendResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/append`,\n {\n path: params.path,\n content: params.content,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * 搜尋檔案內容\n */\n async searchFiles(\n bookToken: string,\n params: { query: string; fileTypes?: string[]; limit?: number }\n ): Promise<McpSearchResponse> {\n return this.request<McpSearchResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/search`,\n {\n query: params.query,\n file_types: params.fileTypes,\n limit: params.limit,\n }\n );\n }\n}\n","/**\n * 書籍相關 Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport type { FileSnapshot } from '../api/types.js';\n\n/**\n * 註冊書籍相關工具\n */\nexport function registerBookTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === create_book ===\n server.tool(\n 'create_book',\n 'Create a new book in your Slima library',\n {\n title: z.string().describe('Book title (required)'),\n author_name: z.string().optional().describe('Author name (optional)'),\n description: z.string().optional().describe('Book description (optional)'),\n },\n async ({ title, author_name, description }) => {\n try {\n const book = await client.createBook({\n title,\n authorName: author_name,\n description,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `✅ Book created successfully!\\n\\n- **Title**: ${book.title}\\n- **Token**: ${book.token}\\n- **Author**: ${book.authorName || 'N/A'}\\n\\nYou can now add files using create_file with book_token: ${book.token}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === list_books ===\n server.tool(\n 'list_books',\n 'List all books in your Slima library',\n {},\n async () => {\n try {\n const books = await client.listBooks();\n\n if (books.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No books found in your library.',\n },\n ],\n };\n }\n\n const formatted = books\n .map((book) => {\n const words = book.totalWordCount?.toLocaleString() || '0';\n return `- **${book.title}** (${book.token})\\n Author: ${book.authorName || 'N/A'} | Words: ${words}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Found ${books.length} book(s):\\n\\n${formatted}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_book ===\n server.tool(\n 'get_book',\n 'Get details of a specific book',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n },\n async ({ book_token }) => {\n try {\n const book = await client.getBook(book_token);\n\n const text = `# ${book.title}\n\n- **Token**: ${book.token}\n- **Author**: ${book.authorName || 'N/A'}\n- **Description**: ${book.description || 'N/A'}\n- **Language**: ${book.language || 'N/A'}\n- **Total Words**: ${book.totalWordCount?.toLocaleString() || '0'}\n- **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || '0'}\n- **Created**: ${book.createdAt}\n- **Updated**: ${book.updatedAt}`;\n\n return {\n content: [{ type: 'text', text }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_book_structure ===\n server.tool(\n 'get_book_structure',\n 'Get the file/folder structure of a book',\n {\n book_token: z.string().describe('Book token'),\n },\n async ({ book_token }) => {\n try {\n const commits = await client.listCommits(book_token, 1);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n };\n }\n\n const snapshot = commits[0].filesSnapshot;\n const tree = formatFileTree(snapshot);\n\n return {\n content: [\n {\n type: 'text',\n text: `File structure for book ${book_token}:\\n\\n${tree}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_writing_stats ===\n server.tool(\n 'get_writing_stats',\n 'Get writing statistics for a book',\n {\n book_token: z.string().describe('Book token'),\n },\n async ({ book_token }) => {\n try {\n const [book, commits] = await Promise.all([\n client.getBook(book_token),\n client.listCommits(book_token, 10),\n ]);\n\n // 計算最近的寫作進度\n let wordChange = 0;\n if (commits.length > 1) {\n const latest = commits[0].totalWordCount || 0;\n const oldest = commits[commits.length - 1].totalWordCount || 0;\n wordChange = latest - oldest;\n }\n\n const sign = wordChange >= 0 ? '+' : '';\n const progressText =\n commits.length > 1\n ? `${sign}${wordChange.toLocaleString()} words (last ${commits.length} commits)`\n : 'N/A (not enough history)';\n\n const text = `# Writing Stats: ${book.title}\n\n- **Total Words**: ${book.totalWordCount?.toLocaleString() || '0'}\n- **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || '0'}\n- **Recent Progress**: ${progressText}\n- **Commits Analyzed**: ${commits.length}\n- **Language**: ${book.language || 'N/A'}`;\n\n return {\n content: [{ type: 'text', text }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/**\n * 格式化檔案樹\n */\nfunction formatFileTree(files: FileSnapshot[], indent = 0): string {\n return files\n .sort((a, b) => a.position - b.position)\n .map((f) => {\n const prefix = ' '.repeat(indent);\n const icon = f.kind === 'folder' ? '📁' : '📄';\n const words = f.kind === 'file' && f.wordCount ? ` (${f.wordCount} words)` : '';\n const manuscript = f.isManuscript ? ' [M]' : '';\n let line = `${prefix}${icon} ${f.name}${words}${manuscript}`;\n\n if (f.children && f.children.length > 0) {\n line += '\\n' + formatFileTree(f.children, indent + 1);\n }\n\n return line;\n })\n .join('\\n');\n}\n","/**\n * 內容存取 Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport type { FileSnapshot } from '../api/types.js';\n\n/**\n * 註冊內容存取工具\n */\nexport function registerContentTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === get_chapter ===\n server.tool(\n 'get_chapter',\n 'Read the content of a chapter or file from a book',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n file_path: z.string().describe('File path (e.g., /chapters/01.md or chapter-01)'),\n },\n async ({ book_token, file_path }) => {\n try {\n // 1. 取得最新 commit\n const commits = await client.listCommits(book_token, 1);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n };\n }\n\n // 2. 在 snapshot 中找到檔案\n const file = findFileInSnapshot(commits[0].filesSnapshot, file_path);\n\n if (!file) {\n // 列出可用的檔案\n const availableFiles = listAllFiles(commits[0].filesSnapshot);\n const fileList = availableFiles.slice(0, 10).join('\\n- ');\n const moreText =\n availableFiles.length > 10\n ? `\\n... and ${availableFiles.length - 10} more files`\n : '';\n\n return {\n content: [\n {\n type: 'text',\n text: `File not found: ${file_path}\\n\\nAvailable files:\\n- ${fileList}${moreText}\\n\\nTip: Use get_book_structure to see the full file tree.`,\n },\n ],\n isError: true,\n };\n }\n\n if (file.kind === 'folder') {\n return {\n content: [\n {\n type: 'text',\n text: `\"${file_path}\" is a folder, not a file. Please specify a file path.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!file.blobHash) {\n return {\n content: [\n {\n type: 'text',\n text: `File \"${file_path}\" has no content (empty file).`,\n },\n ],\n };\n }\n\n // 3. 下載 blob 內容\n const blobs = await client.downloadBlobs(book_token, [file.blobHash]);\n\n if (blobs.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'Could not retrieve file content. The blob may be missing.',\n },\n ],\n isError: true,\n };\n }\n\n const content = blobs[0].content;\n const wordInfo = file.wordCount ? ` (${file.wordCount} words)` : '';\n\n return {\n content: [\n {\n type: 'text',\n text: `# ${file.name}${wordInfo}\\n\\n${content}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/**\n * 在 snapshot 中尋找檔案\n * 支援多種路徑格式:\n * - 完整路徑:/chapters/01.md\n * - 相對路徑:chapters/01.md\n * - 檔名:01.md\n * - Token:chapter-01\n */\nfunction findFileInSnapshot(\n files: FileSnapshot[],\n path: string\n): FileSnapshot | null {\n // 正規化路徑\n const normalizedPath = path.replace(/^\\//, '').toLowerCase();\n const pathParts = normalizedPath.split('/');\n\n // 遞迴搜尋\n function search(\n items: FileSnapshot[],\n remainingParts: string[]\n ): FileSnapshot | null {\n for (const item of items) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n // 完整路徑匹配\n if (remainingParts.length === 1) {\n const target = remainingParts[0];\n if (\n itemNameLower === target ||\n itemTokenLower === target ||\n itemNameLower.replace(/\\.[^.]+$/, '') === target.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n }\n\n // 資料夾匹配,繼續往下搜尋\n if (item.kind === 'folder' && item.children) {\n if (\n itemNameLower === remainingParts[0] ||\n itemTokenLower === remainingParts[0]\n ) {\n const result = search(item.children, remainingParts.slice(1));\n if (result) return result;\n }\n\n // 也嘗試在子資料夾中直接搜尋\n const result = search(item.children, remainingParts);\n if (result) return result;\n }\n }\n\n return null;\n }\n\n // 先嘗試完整路徑\n let result = search(files, pathParts);\n if (result) return result;\n\n // 再嘗試只用檔名搜尋\n result = searchByName(files, pathParts[pathParts.length - 1]);\n return result;\n}\n\n/**\n * 只用檔名搜尋\n */\nfunction searchByName(\n files: FileSnapshot[],\n name: string\n): FileSnapshot | null {\n const nameLower = name.toLowerCase();\n\n for (const item of files) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (\n itemNameLower === nameLower ||\n itemTokenLower === nameLower ||\n itemNameLower.replace(/\\.[^.]+$/, '') === nameLower.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n\n if (item.kind === 'folder' && item.children) {\n const result = searchByName(item.children, name);\n if (result) return result;\n }\n }\n\n return null;\n}\n\n/**\n * 列出所有檔案路徑\n */\nfunction listAllFiles(files: FileSnapshot[], prefix = ''): string[] {\n const result: string[] = [];\n\n for (const item of files) {\n const path = prefix ? `${prefix}/${item.name}` : item.name;\n\n if (item.kind === 'file') {\n result.push(path);\n } else if (item.children) {\n result.push(...listAllFiles(item.children, path));\n }\n }\n\n return result;\n}\n","/**\n * Beta Reader Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport type { FileSnapshot, ReaderTest, IndividualFeedback, AggregatedMetrics } from '../api/types.js';\nimport { logger } from '../utils/logger.js';\n\n/**\n * 註冊 Beta Reader 工具\n */\nexport function registerBetaReaderTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === list_personas ===\n server.tool(\n 'list_personas',\n 'List available virtual reader personas for beta reading feedback',\n {\n genre: z\n .string()\n .optional()\n .describe('Filter by genre (e.g., fantasy, romance, scifi)'),\n },\n async ({ genre }) => {\n try {\n const personas = await client.listPersonas(genre);\n\n if (personas.length === 0) {\n const genreText = genre ? ` for genre \"${genre}\"` : '';\n return {\n content: [\n {\n type: 'text',\n text: `No personas found${genreText}.`,\n },\n ],\n };\n }\n\n const formatted = personas\n .map((p) => {\n const name = p.displayLabels?.zh || p.displayLabels?.en || p.slug;\n const tags = p.genreTags?.join(', ') || 'General';\n const demo = p.demographics || {};\n const gender = demo.gender || 'N/A';\n const age = demo.ageRange || 'N/A';\n\n return `- **${name}** (\\`${p.token}\\`)\n Gender: ${gender} | Age: ${age}\n Genres: ${tags}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Available Personas (${personas.length}):\\n\\n${formatted}\\n\\n**Tip**: Use the persona token with \\`analyze_chapter\\` to get feedback from a specific reader.`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === analyze_chapter ===\n server.tool(\n 'analyze_chapter',\n 'Get AI Beta Reader feedback on a chapter from a specific reader persona. This may take a moment to complete.',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n file_path: z.string().describe('Chapter file path (e.g., /chapters/01.md)'),\n persona_token: z\n .string()\n .describe('Persona token (e.g., psn_xxx). Use list_personas to find available personas.'),\n },\n async ({ book_token, file_path, persona_token }) => {\n try {\n // 1. 取得最新 commit 和章節內容\n const commits = await client.listCommits(book_token, 1);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n isError: true,\n };\n }\n\n // 2. 找到檔案\n const file = findFileInSnapshot(commits[0].filesSnapshot, file_path);\n\n if (!file) {\n return {\n content: [\n {\n type: 'text',\n text: `File not found: ${file_path}. Use get_book_structure to see available files.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!file.blobHash) {\n return {\n content: [\n {\n type: 'text',\n text: `File \"${file_path}\" has no content (empty file).`,\n },\n ],\n isError: true,\n };\n }\n\n // 3. 下載內容\n const blobs = await client.downloadBlobs(book_token, [file.blobHash]);\n\n if (blobs.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'Could not retrieve file content.',\n },\n ],\n isError: true,\n };\n }\n\n const content = blobs[0].content;\n\n // 4. 建立 Reader Test\n logger.info(`Creating reader test for ${file_path} with persona ${persona_token}`);\n\n const test = await client.createReaderTest(book_token, {\n reportType: 'chapter',\n personaTokens: [persona_token],\n commitToken: commits[0].token,\n scopeConfig: { chapters: [file_path] },\n content: content,\n });\n\n // 5. 輪詢等待完成(最多 90 秒)\n const result = await pollForCompletion(\n () => client.getReaderTestProgress(book_token, test.token),\n 90000\n );\n\n if (result.status === 'failed') {\n return {\n content: [\n {\n type: 'text',\n text: 'Analysis failed. Please try again later.',\n },\n ],\n isError: true,\n };\n }\n\n if (result.status !== 'completed') {\n return {\n content: [\n {\n type: 'text',\n text: `Analysis is still in progress. You can check the status later.\\n\\nTest ID: ${test.token}`,\n },\n ],\n };\n }\n\n // 6. 取得完整結果\n const fullResult = await client.getReaderTest(book_token, test.token);\n\n // 7. 格式化反饋\n const feedback = formatBetaReaderFeedback(fullResult, file.name);\n\n return {\n content: [{ type: 'text', text: feedback }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/**\n * 輪詢等待完成\n */\nasync function pollForCompletion(\n checkFn: () => Promise<{ status: string; progress?: number }>,\n timeoutMs: number\n): Promise<{ status: string }> {\n const startTime = Date.now();\n const pollInterval = 3000; // 3 秒\n\n while (Date.now() - startTime < timeoutMs) {\n const result = await checkFn();\n logger.debug(`Poll status: ${result.status}, progress: ${result.progress || 'N/A'}`);\n\n if (result.status === 'completed' || result.status === 'failed') {\n return result;\n }\n\n await sleep(pollInterval);\n }\n\n return { status: 'timeout' };\n}\n\n/**\n * 睡眠\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * 格式化 Beta Reader 反饋 (English headers for better AI processing)\n */\nfunction formatBetaReaderFeedback(test: ReaderTest, fileName: string): string {\n const feedbacks = test.individualFeedbacks || [];\n const metrics = test.aggregatedMetrics;\n\n if (feedbacks.length === 0 && !metrics) {\n return 'No feedback available.';\n }\n\n let output = `# Beta Reader Feedback: ${fileName}\\n\\n`;\n\n // Aggregated metrics summary\n if (metrics) {\n output += formatAggregatedMetrics(metrics);\n }\n\n // Individual persona feedback\n if (feedbacks.length > 0) {\n output += `\\n---\\n\\n## Individual Reader Feedback\\n\\n`;\n output += feedbacks.map(formatIndividualFeedback).join('\\n---\\n\\n');\n }\n\n return output;\n}\n\n/**\n * 格式化聚合指標 (English headers for better AI processing)\n */\nfunction formatAggregatedMetrics(metrics: AggregatedMetrics): string {\n let output = `## Summary Metrics\\n\\n`;\n\n // Overall\n if (metrics.overall) {\n const o = metrics.overall;\n output += `### Overall Scores\\n`;\n if (o.avgContinueReading) output += `- Continue Reading Intent: ${o.avgContinueReading.toFixed(1)}/10\\n`;\n if (o.avgRecommendation) output += `- Recommendation Score: ${o.avgRecommendation.toFixed(1)}/10\\n`;\n if (o.avgWantMore) output += `- Want More: ${o.avgWantMore.toFixed(1)}/10\\n`;\n if (o.dnfRisk) output += `- DNF Risk: ${o.dnfRisk}\\n`;\n output += '\\n';\n }\n\n // Kindle Rating\n if (metrics.kindleRating) {\n output += `### Predicted Kindle Rating\\n`;\n output += `- Average Score: ⭐ ${metrics.kindleRating.avgScore.toFixed(1)}/5\\n`;\n if (metrics.kindleRating.individualScores) {\n const scores = metrics.kindleRating.individualScores\n .map(s => `${s.personaName || 'Reader'}: ${s.score}/5 (${s.confidence})`)\n .join(', ');\n output += `- Individual Scores: ${scores}\\n`;\n }\n output += '\\n';\n }\n\n // Characters\n if (metrics.characters) {\n const c = metrics.characters;\n output += `### Character Evaluation\\n`;\n if (c.avgProtagonistLikability) output += `- Protagonist Likability: ${c.avgProtagonistLikability.toFixed(1)}/10\\n`;\n if (c.avgMotivationClarity) output += `- Motivation Clarity: ${c.avgMotivationClarity.toFixed(1)}/10\\n`;\n if (c.avgDialogueNaturalness) output += `- Dialogue Naturalness: ${c.avgDialogueNaturalness.toFixed(1)}/10\\n`;\n output += '\\n';\n }\n\n // Pacing\n if (metrics.pacing) {\n const p = metrics.pacing;\n output += `### Pacing Evaluation\\n`;\n if (p.avgOverall) output += `- Overall Pacing: ${p.avgOverall.toFixed(1)}/10\\n`;\n if (p.consensus) output += `- Consensus: ${p.consensus}\\n`;\n if (p.commonDnfTriggers && p.commonDnfTriggers.length > 0) {\n output += `- Common DNF Triggers:\\n`;\n p.commonDnfTriggers.slice(0, 3).forEach(t => {\n output += ` - ${t.trigger} (${t.count}x)\\n`;\n });\n }\n output += '\\n';\n }\n\n // Market\n if (metrics.market) {\n const m = metrics.market;\n output += `### Market Positioning\\n`;\n if (m.avgWordOfMouth) output += `- Word of Mouth Potential: ${m.avgWordOfMouth.toFixed(1)}/10\\n`;\n if (m.topComparableBooks && m.topComparableBooks.length > 0) {\n const books = m.topComparableBooks.slice(0, 3).map(b => b.title).join(', ');\n output += `- Comparable Books: ${books}\\n`;\n }\n output += '\\n';\n }\n\n return output;\n}\n\n/**\n * 格式化單一讀者反饋 (English headers for better AI processing)\n */\nfunction formatIndividualFeedback(feedback: IndividualFeedback): string {\n const name = feedback.personaName || feedback.personaToken;\n let output = `### ${name}\\n\\n`;\n\n // Overall scores\n if (feedback.overall) {\n const o = feedback.overall;\n if (o.continueReading) output += `**Continue Reading Intent**: ${o.continueReading}/10\\n`;\n if (o.recommendation) output += `**Recommendation**: ${o.recommendation}/10\\n`;\n if (o.dnfRisk) output += `**DNF Risk**: ${o.dnfRisk}\\n`;\n output += '\\n';\n }\n\n // Kindle Rating\n if (feedback.kindleRating) {\n output += `**Kindle Rating**: ⭐ ${feedback.kindleRating.score}/5`;\n if (feedback.kindleRating.rationale) {\n output += ` - ${feedback.kindleRating.rationale}`;\n }\n output += '\\n\\n';\n }\n\n // Detailed Feedback\n if (feedback.detailedFeedback) {\n const d = feedback.detailedFeedback;\n\n if (d.whatWorked) {\n output += `**What Worked**:\\n${d.whatWorked}\\n\\n`;\n }\n\n if (d.whatDidntWork) {\n output += `**What Didn't Work**:\\n${d.whatDidntWork}\\n\\n`;\n }\n\n if (d.strongestElement) {\n output += `**Strongest Element**: ${d.strongestElement}\\n`;\n }\n\n if (d.weakestElement) {\n output += `**Weakest Element**: ${d.weakestElement}\\n`;\n }\n\n if (d.wouldYouBuy) {\n output += `**Would You Buy**: ${d.wouldYouBuy}\\n`;\n }\n\n if (d.specificSuggestions && d.specificSuggestions.length > 0) {\n output += `\\n**Specific Suggestions**:\\n`;\n d.specificSuggestions.forEach((s, i) => {\n output += `${i + 1}. ${s}\\n`;\n });\n }\n\n if (d.finalThoughts) {\n output += `\\n**Final Thoughts**:\\n${d.finalThoughts}\\n`;\n }\n }\n\n // Emotional Journey\n if (feedback.emotionalJourney) {\n const e = feedback.emotionalJourney;\n if (e.emotionalArc) {\n output += `\\n**Emotional Arc**: ${e.emotionalArc}\\n`;\n }\n if (e.peakMoments && e.peakMoments.length > 0) {\n const moments = e.peakMoments.map(m =>\n typeof m === 'string' ? m : (m as { moment?: string; description?: string }).moment || (m as { moment?: string; description?: string }).description || JSON.stringify(m)\n );\n output += `**Peak Moments**: ${moments.join(', ')}\\n`;\n }\n if (e.overallMood) {\n output += `**Overall Mood**: ${e.overallMood}\\n`;\n }\n }\n\n return output;\n}\n\n/**\n * 在 snapshot 中尋找檔案(複製自 content.ts)\n */\nfunction findFileInSnapshot(\n files: FileSnapshot[],\n path: string\n): FileSnapshot | null {\n const normalizedPath = path.replace(/^\\//, '').toLowerCase();\n const pathParts = normalizedPath.split('/');\n\n function search(\n items: FileSnapshot[],\n remainingParts: string[]\n ): FileSnapshot | null {\n for (const item of items) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (remainingParts.length === 1) {\n const target = remainingParts[0];\n if (\n itemNameLower === target ||\n itemTokenLower === target ||\n itemNameLower.replace(/\\.[^.]+$/, '') === target.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n }\n\n if (item.kind === 'folder' && item.children) {\n if (\n itemNameLower === remainingParts[0] ||\n itemTokenLower === remainingParts[0]\n ) {\n const result = search(item.children, remainingParts.slice(1));\n if (result) return result;\n }\n\n const result = search(item.children, remainingParts);\n if (result) return result;\n }\n }\n\n return null;\n }\n\n let result = search(files, pathParts);\n if (result) return result;\n\n result = searchByName(files, pathParts[pathParts.length - 1]);\n return result;\n}\n\nfunction searchByName(\n files: FileSnapshot[],\n name: string\n): FileSnapshot | null {\n const nameLower = name.toLowerCase();\n\n for (const item of files) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (\n itemNameLower === nameLower ||\n itemTokenLower === nameLower ||\n itemNameLower.replace(/\\.[^.]+$/, '') === nameLower.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n\n if (item.kind === 'folder' && item.children) {\n const result = searchByName(item.children, name);\n if (result) return result;\n }\n }\n\n return null;\n}\n","/**\n * MCP File Operations Tools\n *\n * Provides tools for AI to read, write, create, delete, and search files in books.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\n/**\n * Register file operation tools\n */\nexport function registerFileTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === read_file ===\n server.tool(\n 'read_file',\n 'Read the content of any file in a book by its path',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"characters/protagonist.md\" or \"chapter-01.md\")'),\n },\n async ({ book_token, path }) => {\n try {\n const result = await client.readFile(book_token, path);\n\n const fileInfo = [\n `# ${result.file.name}`,\n '',\n `**Path:** ${result.file.path}`,\n `**Words:** ${result.file.wordCount}`,\n `**Type:** ${result.file.fileType || 'unknown'}`,\n '',\n '---',\n '',\n result.content || '(empty file)',\n ].join('\\n');\n\n return {\n content: [{ type: 'text', text: fileInfo }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === write_file ===\n server.tool(\n 'write_file',\n 'Replace the entire content of an existing file (creates a new commit)',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"characters/protagonist.md\")'),\n content: z.string().describe('New content to write'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.updateFile(book_token, {\n path,\n content,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `File updated successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === edit_file ===\n server.tool(\n 'edit_file',\n 'Edit a file by replacing a specific text segment (search and replace). Use this for precise edits instead of rewriting the entire file.',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"characters/protagonist.md\")'),\n old_string: z.string().describe('The exact text to find and replace (must match exactly, including whitespace)'),\n new_string: z.string().describe('The new text to replace it with'),\n replace_all: z.boolean().optional().describe('Replace all occurrences (default: false, replaces only first match)'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, old_string, new_string, replace_all, commit_message }) => {\n try {\n // 1. Read current content\n const fileData = await client.readFile(book_token, path);\n const currentContent = fileData.content || '';\n\n // 2. Check if old_string exists\n if (!currentContent.includes(old_string)) {\n return {\n content: [\n {\n type: 'text',\n text: `❌ Edit failed: Could not find the specified text in the file.\\n\\n**Looking for:**\\n\\`\\`\\`\\n${old_string.slice(0, 200)}${old_string.length > 200 ? '...' : ''}\\n\\`\\`\\`\\n\\n**Tip:** Make sure the text matches exactly, including whitespace and line breaks.`,\n },\n ],\n isError: true,\n };\n }\n\n // 3. Check for uniqueness (if not replace_all)\n if (!replace_all) {\n const occurrences = currentContent.split(old_string).length - 1;\n if (occurrences > 1) {\n return {\n content: [\n {\n type: 'text',\n text: `❌ Edit failed: Found ${occurrences} occurrences of the text. Please provide a more specific/longer text segment to ensure a unique match, or set replace_all: true to replace all occurrences.`,\n },\n ],\n isError: true,\n };\n }\n }\n\n // 4. Perform replacement\n const newContent = replace_all\n ? currentContent.split(old_string).join(new_string)\n : currentContent.replace(old_string, new_string);\n\n // 5. Write back\n const result = await client.updateFile(book_token, {\n path,\n content: newContent,\n commitMessage: commit_message || `Edit ${path}`,\n });\n\n const replacementCount = replace_all\n ? currentContent.split(old_string).length - 1\n : 1;\n\n return {\n content: [\n {\n type: 'text',\n text: `✅ File edited successfully.\\n\\n**Replacements:** ${replacementCount}\\n**Commit:** ${result.commit.token}\\n**Message:** ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === create_file ===\n server.tool(\n 'create_file',\n 'Create a new file in a book (creates a new commit)',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path including parent folders (e.g., \"characters/new-character.md\")'),\n content: z.string().optional().describe('Initial content (optional, defaults to empty)'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.createFile(book_token, {\n path,\n content: content || '',\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `File created successfully.\\n\\nFile Token: ${result.fileToken}\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === delete_file ===\n server.tool(\n 'delete_file',\n 'Delete a file from a book (creates a new commit)',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path to delete (e.g., \"old-notes.md\")'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, commit_message }) => {\n try {\n const result = await client.deleteFile(book_token, {\n path,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `File deleted successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === append_to_file ===\n server.tool(\n 'append_to_file',\n 'Append content to the end of an existing file (creates a new commit)',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"notes.md\")'),\n content: z.string().describe('Content to append'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.appendToFile(book_token, {\n path,\n content,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `Content appended successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === search_content ===\n server.tool(\n 'search_content',\n 'Search for content across all files in a book',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n query: z.string().describe('Search query (e.g., \"blue eyes\" or \"protagonist name\")'),\n file_types: z.array(z.string()).optional().describe('Filter by file types (e.g., [\"markdown\", \"txt\"])'),\n limit: z.number().optional().describe('Maximum number of matches to return (default: 20)'),\n },\n async ({ book_token, query, file_types, limit }) => {\n try {\n const result = await client.searchFiles(book_token, {\n query,\n fileTypes: file_types,\n limit,\n });\n\n if (result.matches.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: `No matches found for \"${query}\".`,\n },\n ],\n };\n }\n\n const output: string[] = [\n `# Search Results for \"${query}\"`,\n '',\n `Found ${result.matches.length} file(s) with matches:`,\n '',\n ];\n\n for (const match of result.matches) {\n output.push(`## ${match.file.path}`);\n output.push(`Words: ${match.file.wordCount} | Matches: ${match.matchCount}`);\n output.push('');\n\n for (const snippet of match.snippets) {\n output.push(`> ${snippet.text}`);\n output.push('');\n }\n }\n\n return {\n content: [{ type: 'text', text: output.join('\\n') }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * Token Storage Utility\n *\n * 將 API Token 儲存在使用者的 home 目錄下\n * ~/.slima/credentials.json\n */\n\nimport { promises as fs } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst SLIMA_DIR = '.slima';\nconst CREDENTIALS_FILE = 'credentials.json';\n\ninterface Credentials {\n apiToken: string;\n createdAt: string;\n baseUrl?: string;\n}\n\nfunction getCredentialsPath(): string {\n return join(homedir(), SLIMA_DIR, CREDENTIALS_FILE);\n}\n\nfunction getSlimaDir(): string {\n return join(homedir(), SLIMA_DIR);\n}\n\n/**\n * 儲存 API Token\n */\nexport async function saveToken(apiToken: string, baseUrl?: string): Promise<void> {\n const slimaDir = getSlimaDir();\n\n // 確保目錄存在\n await fs.mkdir(slimaDir, { recursive: true });\n\n const credentials: Credentials = {\n apiToken,\n createdAt: new Date().toISOString(),\n ...(baseUrl && { baseUrl }),\n };\n\n const credentialsPath = getCredentialsPath();\n await fs.writeFile(credentialsPath, JSON.stringify(credentials, null, 2), 'utf-8');\n\n // 設定檔案權限為 600(僅擁有者可讀寫)\n await fs.chmod(credentialsPath, 0o600);\n}\n\n/**\n * 讀取 API Token\n */\nexport async function loadToken(): Promise<Credentials | null> {\n try {\n const credentialsPath = getCredentialsPath();\n const content = await fs.readFile(credentialsPath, 'utf-8');\n return JSON.parse(content) as Credentials;\n } catch {\n return null;\n }\n}\n\n/**\n * 刪除 API Token\n */\nexport async function deleteToken(): Promise<void> {\n try {\n const credentialsPath = getCredentialsPath();\n await fs.unlink(credentialsPath);\n } catch {\n // 檔案不存在,忽略\n }\n}\n\n/**\n * 檢查是否已認證\n */\nexport async function isAuthenticated(): Promise<boolean> {\n const credentials = await loadToken();\n return credentials !== null && !!credentials.apiToken;\n}\n\n/**\n * 取得 credentials 檔案路徑(供顯示用)\n */\nexport function getCredentialsFilePath(): string {\n return getCredentialsPath();\n}\n","/**\n * CLI Auth Command\n *\n * 實作類似 Claude Code / GitHub CLI 的認證流程:\n * 1. 使用者執行 `slima-mcp auth`\n * 2. 開啟瀏覽器到 Slima 認證頁面\n * 3. 使用者登入並授權\n * 4. 本地伺服器接收 callback 並儲存 token\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport { randomBytes } from 'node:crypto';\nimport { saveToken, loadToken, deleteToken, getCredentialsFilePath } from '../utils/token-storage.js';\n\n// 認證使用 API 伺服器(處理 OAuth 和 session)\nconst DEFAULT_API_URL = 'https://api.slima.ai';\nconst CALLBACK_PORT_START = 8765;\nconst CALLBACK_PORT_END = 8775;\n\n/**\n * 開啟瀏覽器\n */\nasync function openBrowser(url: string): Promise<void> {\n const { exec } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execAsync = promisify(exec);\n\n const platform = process.platform;\n\n try {\n if (platform === 'darwin') {\n await execAsync(`open \"${url}\"`);\n } else if (platform === 'win32') {\n await execAsync(`start \"\" \"${url}\"`);\n } else {\n // Linux / WSL\n // 嘗試多個可能的命令\n const commands = ['xdg-open', 'sensible-browser', 'wslview'];\n for (const cmd of commands) {\n try {\n await execAsync(`${cmd} \"${url}\"`);\n return;\n } catch {\n // 繼續嘗試下一個\n }\n }\n // 如果都失敗,顯示 URL 讓使用者手動開啟\n console.log(`\\nPlease open this URL in your browser:\\n${url}\\n`);\n }\n } catch {\n console.log(`\\nPlease open this URL in your browser:\\n${url}\\n`);\n }\n}\n\n/**\n * 尋找可用的 port\n */\nasync function findAvailablePort(): Promise<number> {\n for (let port = CALLBACK_PORT_START; port <= CALLBACK_PORT_END; port++) {\n const available = await isPortAvailable(port);\n if (available) {\n return port;\n }\n }\n throw new Error(`No available ports between ${CALLBACK_PORT_START} and ${CALLBACK_PORT_END}`);\n}\n\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once('error', () => resolve(false));\n server.once('listening', () => {\n server.close(() => resolve(true));\n });\n server.listen(port, '127.0.0.1');\n });\n}\n\n/**\n * 產生隨機 state(防止 CSRF)\n */\nfunction generateState(): string {\n return randomBytes(16).toString('hex');\n}\n\n/**\n * 成功頁面 HTML\n * Design follows UIUX_SPEC.md: minimalist, grayscale, Inter font\n */\nfunction getSuccessHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Welcome to Slima</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n background: #FBFBFA;\n color: #1D1D1F;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .container {\n text-align: center;\n padding: 48px;\n max-width: 420px;\n }\n .logo {\n width: 48px;\n height: 48px;\n margin-bottom: 32px;\n opacity: 0.9;\n }\n .success-icon {\n width: 64px;\n height: 64px;\n margin-bottom: 24px;\n border-radius: 50%;\n background: #1D1D1F;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n margin-right: auto;\n }\n .success-icon svg {\n width: 32px;\n height: 32px;\n stroke: #FBFBFA;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n letter-spacing: -0.02em;\n margin-bottom: 12px;\n color: #1D1D1F;\n }\n .subtitle {\n font-size: 15px;\n color: #6E6E73;\n line-height: 1.5;\n margin-bottom: 8px;\n }\n .hint {\n font-size: 13px;\n color: #86868B;\n margin-top: 24px;\n }\n .divider {\n width: 40px;\n height: 1px;\n background: #E5E5E5;\n margin: 32px auto;\n }\n .footer {\n font-size: 12px;\n color: #86868B;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <img src=\"https://app.slima.ai/icons/slima-black.svg\" alt=\"Slima\" class=\"logo\">\n <div class=\"success-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.5 12.75l6 6 9-13.5\" />\n </svg>\n </div>\n <h1>Welcome to Slima</h1>\n <p class=\"subtitle\">Authentication successful. Your CLI is now connected.</p>\n <p class=\"hint\">You can close this window and return to your terminal.</p>\n <div class=\"divider\"></div>\n <p class=\"footer\">Slima MCP is ready to use</p>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * 錯誤頁面 HTML\n */\n/**\n * HTML escape to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n };\n return text.replace(/[&<>\"']/g, (char) => map[char]);\n}\n\n/**\n * 錯誤頁面 HTML\n * Design follows UIUX_SPEC.md: minimalist, grayscale, Inter font\n */\nfunction getErrorHtml(message: string): string {\n const safeMessage = escapeHtml(message);\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Slima - Authentication Error</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n background: #FBFBFA;\n color: #1D1D1F;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .container {\n text-align: center;\n padding: 48px;\n max-width: 420px;\n }\n .logo {\n width: 48px;\n height: 48px;\n margin-bottom: 32px;\n opacity: 0.9;\n }\n .error-icon {\n width: 64px;\n height: 64px;\n margin-bottom: 24px;\n border-radius: 50%;\n background: #1D1D1F;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n margin-right: auto;\n }\n .error-icon svg {\n width: 32px;\n height: 32px;\n stroke: #FBFBFA;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n letter-spacing: -0.02em;\n margin-bottom: 12px;\n color: #1D1D1F;\n }\n .message {\n font-size: 15px;\n color: #6E6E73;\n line-height: 1.5;\n margin-bottom: 8px;\n }\n .hint {\n font-size: 13px;\n color: #86868B;\n margin-top: 24px;\n }\n .divider {\n width: 40px;\n height: 1px;\n background: #E5E5E5;\n margin: 32px auto;\n }\n .retry-link {\n display: inline-block;\n font-size: 14px;\n font-weight: 500;\n color: #1D1D1F;\n text-decoration: none;\n padding: 10px 20px;\n border: 1px solid #E5E5E5;\n border-radius: 8px;\n transition: all 0.2s ease;\n }\n .retry-link:hover {\n background: #F5F5F5;\n border-color: #D5D5D5;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <img src=\"https://app.slima.ai/icons/slima-black.svg\" alt=\"Slima\" class=\"logo\">\n <div class=\"error-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </div>\n <h1>Something went wrong</h1>\n <p class=\"message\">${safeMessage}</p>\n <p class=\"hint\">Please close this window and try again from your terminal.</p>\n <div class=\"divider\"></div>\n <a href=\"javascript:window.close()\" class=\"retry-link\">Close Window</a>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * 執行認證流程\n */\nexport async function runAuth(options: { baseUrl?: string } = {}): Promise<void> {\n const baseUrl = options.baseUrl || process.env.SLIMA_API_URL || DEFAULT_API_URL;\n\n console.log('\\n🔐 Slima MCP Authentication\\n');\n\n // 檢查是否已經認證\n const existingCredentials = await loadToken();\n if (existingCredentials) {\n console.log('You are already authenticated.');\n console.log(`Token stored at: ${getCredentialsFilePath()}`);\n console.log('\\nTo re-authenticate, first run: slima-mcp logout\\n');\n return;\n }\n\n const state = generateState();\n const port = await findAvailablePort();\n\n console.log('Starting local server for authentication callback...');\n\n // 建立本地伺服器接收 callback\n const tokenPromise = new Promise<string>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`);\n\n if (url.pathname === '/callback') {\n const token = url.searchParams.get('token');\n const returnedState = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n\n if (error) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml(error));\n server.close();\n reject(new Error(error));\n return;\n }\n\n if (returnedState !== state) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml('Invalid state parameter. Please try again.'));\n server.close();\n reject(new Error('State mismatch'));\n return;\n }\n\n if (!token) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml('No token received. Please try again.'));\n server.close();\n reject(new Error('No token received'));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getSuccessHtml());\n server.close();\n resolve(token);\n } else {\n res.writeHead(404);\n res.end('Not found');\n }\n });\n\n server.listen(port, '127.0.0.1', () => {\n console.log(`Listening on http://127.0.0.1:${port}`);\n });\n\n // 30 秒超時\n setTimeout(() => {\n server.close();\n reject(new Error('Authentication timed out. Please try again.'));\n }, 30000);\n });\n\n // 建立認證 URL(使用 API endpoint)\n const authUrl = `${baseUrl}/api/v1/auth/cli?state=${state}&callback_port=${port}`;\n\n console.log('\\nOpening browser for authentication...');\n await openBrowser(authUrl);\n\n console.log('\\nWaiting for authentication...');\n console.log('(Press Ctrl+C to cancel)\\n');\n\n try {\n const token = await tokenPromise;\n await saveToken(token, baseUrl !== DEFAULT_API_URL ? baseUrl : undefined);\n\n console.log('\\n✅ Authentication successful!');\n console.log(`Token saved to: ${getCredentialsFilePath()}`);\n console.log('\\nYou can now use Slima MCP with any MCP-compatible AI tool.\\n');\n } catch (error) {\n console.error('\\n❌ Authentication failed:', error instanceof Error ? error.message : error);\n process.exit(1);\n }\n}\n\n/**\n * 登出(刪除 token)\n */\nexport async function runLogout(): Promise<void> {\n console.log('\\n🔓 Slima MCP Logout\\n');\n\n const credentials = await loadToken();\n if (!credentials) {\n console.log('You are not authenticated.\\n');\n return;\n }\n\n await deleteToken();\n console.log('✅ Successfully logged out.');\n console.log(`Removed credentials from: ${getCredentialsFilePath()}\\n`);\n}\n\n/**\n * 顯示目前狀態\n */\nexport async function runStatus(): Promise<void> {\n console.log('\\n📊 Slima MCP Status\\n');\n\n const credentials = await loadToken();\n if (!credentials) {\n console.log('Status: Not authenticated');\n console.log('\\nRun `slima-mcp auth` to authenticate.\\n');\n return;\n }\n\n console.log('Status: Authenticated');\n console.log(`Token: ${credentials.apiToken.substring(0, 15)}...`);\n console.log(`Authenticated at: ${credentials.createdAt}`);\n if (credentials.baseUrl) {\n console.log(`API URL: ${credentials.baseUrl}`);\n }\n console.log(`Credentials file: ${getCredentialsFilePath()}\\n`);\n}\n"],"mappings":"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","names":["z","z","result","z","z","findFileInSnapshot","result","searchByName","z","z"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/index.ts","../src/cli/server.ts","../src/core/utils/errors.ts","../src/core/api/client.ts","../src/core/tools/books.ts","../src/core/tools/content.ts","../src/core/tools/beta-reader.ts","../src/core/tools/files.ts","../src/core/utils/logger.ts","../src/cli/token-storage.ts","../src/cli/auth.ts"],"sourcesContent":["/**\n * Slima MCP Server - CLI Entry Point\n *\n * Usage:\n * slima-mcp - Start MCP server (requires SLIMA_API_TOKEN or prior auth)\n * slima-mcp auth - Authenticate with Slima (opens browser)\n * slima-mcp logout - Remove saved credentials\n * slima-mcp status - Show authentication status\n * slima-mcp --help - Show help\n */\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createSlimaServer } from './server.js';\nimport { logger } from '../core/utils/logger.js';\nimport { loadToken, getCredentialsFilePath } from './token-storage.js';\nimport { runAuth, runLogout, runStatus } from './auth.js';\n\ndeclare const __VERSION__: string;\nconst VERSION = __VERSION__;\n\nfunction showHelp(): void {\n console.log(`\nSlima MCP v${VERSION}\nMCP Server for Slima - AI Writing IDE for Novel Authors\n\nUSAGE:\n slima-mcp [command]\n\nCOMMANDS:\n (none) Start the MCP server\n auth Authenticate with Slima (opens browser)\n auth --force Re-authenticate (switch account)\n logout Remove saved credentials\n status Show authentication status\n --help Show this help message\n --version Show version\n\nAUTHENTICATION:\n 1. Run: slima-mcp auth\n 2. Complete authentication in browser\n 3. Token is automatically saved\n\n Or set SLIMA_API_TOKEN environment variable.\n\nEXAMPLES:\n slima-mcp auth # Authenticate with browser\n slima-mcp auth --force # Re-authenticate (switch account)\n slima-mcp status # Check auth status\n SLIMA_API_TOKEN=xxx slima-mcp # Use env var\n\nCONFIGURATION (for AI tools):\n Claude Desktop, Cursor, etc.:\n {\n \"mcpServers\": {\n \"slima\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"slima-mcp\"]\n }\n }\n }\n\nMORE INFO:\n https://github.com/slima-ai/slima-mcp\n`);\n}\n\nfunction showVersion(): void {\n console.log(`slima-mcp v${VERSION}`);\n}\n\nasync function startServer(): Promise<void> {\n // Use env var first, then stored token\n let apiToken = process.env.SLIMA_API_TOKEN;\n let baseUrl = process.env.SLIMA_API_URL;\n\n if (!apiToken) {\n const credentials = await loadToken();\n if (credentials) {\n apiToken = credentials.apiToken;\n baseUrl = baseUrl || credentials.baseUrl;\n }\n }\n\n if (!apiToken) {\n logger.error('Not authenticated and SLIMA_API_TOKEN not set');\n logger.error('');\n logger.error('To authenticate, run:');\n logger.error(' slima-mcp auth');\n logger.error('');\n logger.error('Or set environment variable:');\n logger.error(' SLIMA_API_TOKEN=your_token slima-mcp');\n logger.error('');\n logger.error('Get your API token from: https://app.slima.ai/settings/api-tokens');\n process.exit(1);\n }\n\n try {\n const server = createSlimaServer({\n apiToken,\n baseUrl,\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n logger.info('Slima MCP Server running on stdio');\n\n // Graceful shutdown\n process.on('SIGINT', async () => {\n logger.info('Shutting down...');\n await server.close();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n logger.info('Shutting down...');\n await server.close();\n process.exit(0);\n });\n } catch (error) {\n logger.error('Failed to start server', error);\n process.exit(1);\n }\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0];\n\n switch (command) {\n case 'auth': {\n const forceFlag = args.includes('--force') || args.includes('-f');\n await runAuth({ force: forceFlag });\n break;\n }\n\n case 'logout':\n await runLogout();\n break;\n\n case 'status':\n await runStatus();\n break;\n\n case '--help':\n case '-h':\n case 'help':\n showHelp();\n break;\n\n case '--version':\n case '-v':\n showVersion();\n break;\n\n case undefined:\n // Default: start MCP server\n await startServer();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n console.error('Run `slima-mcp --help` for usage.');\n process.exit(1);\n }\n}\n\nmain();\n","/**\n * Slima MCP Server (CLI/stdio)\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SlimaApiClient } from '../core/api/client.js';\nimport {\n registerBookTools,\n registerContentTools,\n registerBetaReaderTools,\n registerFileTools,\n} from '../core/tools/index.js';\nimport { logger } from '../core/utils/logger.js';\n\ndeclare const __VERSION__: string;\n\nexport interface ServerConfig {\n apiToken: string;\n baseUrl?: string;\n}\n\n/**\n * Create Slima MCP Server\n */\nexport function createSlimaServer(config: ServerConfig): McpServer {\n // Create API Client with static token getter (for CLI)\n const client = new SlimaApiClient({\n baseUrl: config.baseUrl || 'https://api.slima.ai',\n getToken: async () => config.apiToken,\n logger,\n });\n\n const server = new McpServer({\n name: 'slima',\n version: __VERSION__,\n });\n\n // Register tools\n registerBookTools(server, client);\n registerContentTools(server, client);\n registerBetaReaderTools(server, client, logger);\n registerFileTools(server, client);\n\n logger.info('Slima MCP Server initialized');\n logger.info(`API endpoint: ${config.baseUrl || 'https://api.slima.ai'}`);\n\n return server;\n}\n","/**\n * 錯誤處理工具\n */\n\nexport class SlimaApiError extends Error {\n constructor(\n public status: number,\n public code?: string,\n message?: string\n ) {\n super(message || `API Error: ${status}`);\n this.name = 'SlimaApiError';\n }\n}\n\nexport class AuthenticationError extends SlimaApiError {\n constructor(message = 'Invalid or expired API token') {\n super(401, 'UNAUTHORIZED', message);\n this.name = 'AuthenticationError';\n }\n}\n\nexport class NotFoundError extends SlimaApiError {\n constructor(resource = 'Resource') {\n super(404, 'NOT_FOUND', `${resource} not found`);\n this.name = 'NotFoundError';\n }\n}\n\nexport class InsufficientCreditsError extends SlimaApiError {\n constructor() {\n super(402, 'INSUFFICIENT_CREDITS', 'Insufficient credits');\n this.name = 'InsufficientCreditsError';\n }\n}\n\n/**\n * 格式化錯誤訊息供 MCP 回傳\n */\nexport function formatErrorForMcp(error: unknown): string {\n if (error instanceof SlimaApiError) {\n switch (error.code) {\n case 'NOT_FOUND':\n return 'Resource not found. Please check the token.';\n case 'UNAUTHORIZED':\n return 'Invalid API token. Please check your configuration.';\n case 'INSUFFICIENT_CREDITS':\n return 'Insufficient credits. Please top up your account.';\n case 'TOKEN_EXPIRED':\n return 'API token has expired. Please generate a new one.';\n case 'TOKEN_REVOKED':\n return 'API token has been revoked. Please generate a new one.';\n default:\n return error.message;\n }\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return 'An unexpected error occurred.';\n}\n","/**\n * Slima API Client\n *\n * Supports dependency injection for token retrieval,\n * allowing different strategies for CLI (file-based) vs Worker (KV-based).\n */\n\nimport {\n type Book,\n type Commit,\n type Branch,\n type Blob,\n type Persona,\n type ReaderTest,\n type ReaderTestProgress,\n type CreateReaderTestParams,\n type ApiResponse,\n type ApiError,\n type CommitsListResponse,\n type BlobsDownloadResponse,\n type McpFileReadResponse,\n type McpFileCreateResponse,\n type McpFileUpdateResponse,\n type McpFileDeleteResponse,\n type McpFileAppendResponse,\n type McpSearchResponse,\n} from './types.js';\nimport {\n SlimaApiError,\n AuthenticationError,\n NotFoundError,\n InsufficientCreditsError,\n} from '../utils/errors.js';\n\n// Export for logger injection (optional)\nexport interface Logger {\n debug: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n error: (message: string, error?: unknown) => void;\n}\n\n// Default no-op logger\nconst defaultLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\n/**\n * API Client Configuration (new style with token getter)\n */\nexport interface ApiClientConfig {\n baseUrl: string;\n getToken: () => Promise<string>; // Async token getter (injected)\n logger?: Logger;\n}\n\n/**\n * Legacy config format (backwards compatibility)\n * @deprecated Use ApiClientConfig with getToken instead\n */\nexport interface SlimaConfig {\n baseUrl: string;\n token: string;\n}\n\n// Type guard to check if config is legacy format\nfunction isLegacyConfig(config: ApiClientConfig | SlimaConfig): config is SlimaConfig {\n return 'token' in config && typeof (config as SlimaConfig).token === 'string';\n}\n\nexport class SlimaApiClient {\n private readonly baseUrl: string;\n private readonly getToken: () => Promise<string>;\n private readonly logger: Logger;\n\n constructor(config: ApiClientConfig | SlimaConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n // Support both legacy and new config formats\n if (isLegacyConfig(config)) {\n const staticToken = config.token;\n this.getToken = async () => staticToken;\n this.logger = defaultLogger;\n } else {\n this.getToken = config.getToken;\n this.logger = config.logger || defaultLogger;\n }\n }\n\n /**\n * Send API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const token = await this.getToken();\n\n this.logger.debug(`${method} ${path}`);\n\n const response = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'slima-mcp-server/0.1.0',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n const json = (await response.json()) as ApiResponse<T>;\n return json.data;\n }\n\n /**\n * Handle error response\n */\n private async handleErrorResponse(response: Response): Promise<never> {\n let errorData: ApiError | undefined;\n\n try {\n errorData = (await response.json()) as ApiError;\n } catch {\n // Cannot parse JSON\n }\n\n const code = errorData?.error?.code;\n const message = errorData?.error?.message;\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message);\n case 404:\n throw new NotFoundError(message);\n case 402:\n throw new InsufficientCreditsError();\n default:\n throw new SlimaApiError(response.status, code, message);\n }\n }\n\n // === Book Operations ===\n\n /**\n * List all books\n */\n async listBooks(): Promise<Book[]> {\n return this.request<Book[]>('GET', '/api/v1/books');\n }\n\n /**\n * Get book details\n */\n async getBook(token: string): Promise<Book> {\n return this.request<Book>('GET', `/api/v1/books/${token}`);\n }\n\n /**\n * Create a new book\n */\n async createBook(params: {\n title: string;\n authorName?: string;\n description?: string;\n }): Promise<Book> {\n return this.request<Book>('POST', '/api/v1/books', {\n book: {\n title: params.title,\n author_name: params.authorName,\n description: params.description,\n },\n });\n }\n\n // === Version Control ===\n\n /**\n * List commits\n */\n async listCommits(bookToken: string, limit = 10): Promise<Commit[]> {\n const response = await this.request<CommitsListResponse>(\n 'GET',\n `/api/v1/books/${bookToken}/commits?limit=${limit}`\n );\n return response.commits;\n }\n\n /**\n * Get commit details\n */\n async getCommit(bookToken: string, commitToken: string): Promise<Commit> {\n return this.request<Commit>(\n 'GET',\n `/api/v1/books/${bookToken}/commits/${commitToken}`\n );\n }\n\n /**\n * List branches\n */\n async listBranches(bookToken: string): Promise<Branch[]> {\n return this.request<Branch[]>('GET', `/api/v1/books/${bookToken}/branches`);\n }\n\n /**\n * Download blobs\n */\n async downloadBlobs(bookToken: string, hashes: string[]): Promise<Blob[]> {\n const response = await this.request<BlobsDownloadResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/blobs/download`,\n { hashes }\n );\n return response.blobs;\n }\n\n // === Persona ===\n\n /**\n * List personas\n */\n async listPersonas(genre?: string): Promise<Persona[]> {\n const params = genre ? `?genre=${encodeURIComponent(genre)}` : '';\n return this.request<Persona[]>('GET', `/api/v1/personas${params}`);\n }\n\n /**\n * Get persona details\n */\n async getPersona(token: string): Promise<Persona> {\n return this.request<Persona>('GET', `/api/v1/personas/${token}`);\n }\n\n // === Reader Test ===\n\n /**\n * Create reader test\n */\n async createReaderTest(\n bookToken: string,\n params: CreateReaderTestParams\n ): Promise<ReaderTest> {\n return this.request<ReaderTest>(\n 'POST',\n `/api/v1/books/${bookToken}/reader_tests`,\n {\n reader_test: {\n report_type: params.reportType || 'chapter',\n persona_tokens: params.personaTokens,\n commit_token: params.commitToken,\n model: params.model,\n scope_config: params.scopeConfig,\n },\n content: params.content,\n }\n );\n }\n\n /**\n * Get reader test progress\n */\n async getReaderTestProgress(\n bookToken: string,\n testToken: string\n ): Promise<ReaderTestProgress> {\n return this.request<ReaderTestProgress>(\n 'GET',\n `/api/v1/books/${bookToken}/reader_tests/${testToken}/progress`\n );\n }\n\n /**\n * Get reader test result\n */\n async getReaderTest(bookToken: string, testToken: string): Promise<ReaderTest> {\n return this.request<ReaderTest>(\n 'GET',\n `/api/v1/books/${bookToken}/reader_tests/${testToken}`\n );\n }\n\n // === MCP File Operations ===\n\n /**\n * Read file content\n */\n async readFile(bookToken: string, path: string): Promise<McpFileReadResponse> {\n return this.request<McpFileReadResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/read`,\n { path }\n );\n }\n\n /**\n * Create file\n */\n async createFile(\n bookToken: string,\n params: { path: string; content?: string; parentPath?: string; commitMessage?: string }\n ): Promise<McpFileCreateResponse> {\n return this.request<McpFileCreateResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/create`,\n {\n path: params.path,\n content: params.content || '',\n parent_path: params.parentPath,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Update (overwrite) file content\n */\n async updateFile(\n bookToken: string,\n params: { path: string; content: string; commitMessage?: string }\n ): Promise<McpFileUpdateResponse> {\n return this.request<McpFileUpdateResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/update`,\n {\n path: params.path,\n content: params.content,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Delete file\n */\n async deleteFile(\n bookToken: string,\n params: { path: string; commitMessage?: string }\n ): Promise<McpFileDeleteResponse> {\n return this.request<McpFileDeleteResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/delete`,\n {\n path: params.path,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Append content to file\n */\n async appendToFile(\n bookToken: string,\n params: { path: string; content: string; commitMessage?: string }\n ): Promise<McpFileAppendResponse> {\n return this.request<McpFileAppendResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/append`,\n {\n path: params.path,\n content: params.content,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Search file content\n */\n async searchFiles(\n bookToken: string,\n params: { query: string; fileTypes?: string[]; limit?: number }\n ): Promise<McpSearchResponse> {\n return this.request<McpSearchResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/search`,\n {\n query: params.query,\n file_types: params.fileTypes,\n limit: params.limit,\n }\n );\n }\n}\n","/**\n * Book Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport type { FileSnapshot } from '../api/types.js';\n\n/**\n * Register book-related tools\n */\nexport function registerBookTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === create_book ===\n server.tool(\n 'create_book',\n 'Create a new book in your Slima library',\n {\n title: z.string().describe('Book title (required)'),\n author_name: z.string().optional().describe('Author name (optional)'),\n description: z.string().optional().describe('Book description (optional)'),\n },\n async ({ title, author_name, description }) => {\n try {\n const book = await client.createBook({\n title,\n authorName: author_name,\n description,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `✅ Book created successfully!\\n\\n- **Title**: ${book.title}\\n- **Token**: ${book.token}\\n- **Author**: ${book.authorName || 'N/A'}\\n\\nYou can now add files using create_file with book_token: ${book.token}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === list_books ===\n server.tool(\n 'list_books',\n 'List all books in your Slima library',\n {},\n async () => {\n try {\n const books = await client.listBooks();\n\n if (books.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No books found in your library.',\n },\n ],\n };\n }\n\n const formatted = books\n .map((book) => {\n const words = book.totalWordCount?.toLocaleString() || '0';\n return `- **${book.title}** (${book.token})\\n Author: ${book.authorName || 'N/A'} | Words: ${words}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Found ${books.length} book(s):\\n\\n${formatted}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_book ===\n server.tool(\n 'get_book',\n 'Get details of a specific book',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n },\n async ({ book_token }) => {\n try {\n const book = await client.getBook(book_token);\n\n const text = `# ${book.title}\n\n- **Token**: ${book.token}\n- **Author**: ${book.authorName || 'N/A'}\n- **Description**: ${book.description || 'N/A'}\n- **Language**: ${book.language || 'N/A'}\n- **Total Words**: ${book.totalWordCount?.toLocaleString() || '0'}\n- **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || '0'}\n- **Created**: ${book.createdAt}\n- **Updated**: ${book.updatedAt}`;\n\n return {\n content: [{ type: 'text', text }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_book_structure ===\n server.tool(\n 'get_book_structure',\n 'Get the file/folder structure of a book',\n {\n book_token: z.string().describe('Book token'),\n },\n async ({ book_token }) => {\n try {\n const commits = await client.listCommits(book_token, 1);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n };\n }\n\n const snapshot = commits[0].filesSnapshot;\n const tree = formatFileTree(snapshot);\n\n return {\n content: [\n {\n type: 'text',\n text: `File structure for book ${book_token}:\\n\\n${tree}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_writing_stats ===\n server.tool(\n 'get_writing_stats',\n 'Get writing statistics for a book',\n {\n book_token: z.string().describe('Book token'),\n },\n async ({ book_token }) => {\n try {\n const [book, commits] = await Promise.all([\n client.getBook(book_token),\n client.listCommits(book_token, 10),\n ]);\n\n // Calculate recent progress\n let wordChange = 0;\n if (commits.length > 1) {\n const latest = commits[0].totalWordCount || 0;\n const oldest = commits[commits.length - 1].totalWordCount || 0;\n wordChange = latest - oldest;\n }\n\n const sign = wordChange >= 0 ? '+' : '';\n const progressText =\n commits.length > 1\n ? `${sign}${wordChange.toLocaleString()} words (last ${commits.length} commits)`\n : 'N/A (not enough history)';\n\n const text = `# Writing Stats: ${book.title}\n\n- **Total Words**: ${book.totalWordCount?.toLocaleString() || '0'}\n- **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || '0'}\n- **Recent Progress**: ${progressText}\n- **Commits Analyzed**: ${commits.length}\n- **Language**: ${book.language || 'N/A'}`;\n\n return {\n content: [{ type: 'text', text }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/**\n * Format file tree\n */\nfunction formatFileTree(files: FileSnapshot[], indent = 0): string {\n return files\n .sort((a, b) => a.position - b.position)\n .map((f) => {\n const prefix = ' '.repeat(indent);\n const icon = f.kind === 'folder' ? '📁' : '📄';\n const words = f.kind === 'file' && f.wordCount ? ` (${f.wordCount} words)` : '';\n const manuscript = f.isManuscript ? ' [M]' : '';\n let line = `${prefix}${icon} ${f.name}${words}${manuscript}`;\n\n if (f.children && f.children.length > 0) {\n line += '\\n' + formatFileTree(f.children, indent + 1);\n }\n\n return line;\n })\n .join('\\n');\n}\n","/**\n * Content Access Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport type { FileSnapshot } from '../api/types.js';\n\n/**\n * Register content access tools\n */\nexport function registerContentTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === get_chapter ===\n server.tool(\n 'get_chapter',\n 'Read the content of a chapter or file from a book',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n file_path: z.string().describe('File path (e.g., /chapters/01.md or chapter-01)'),\n },\n async ({ book_token, file_path }) => {\n try {\n // 1. Get latest commit\n const commits = await client.listCommits(book_token, 1);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n };\n }\n\n // 2. Find file in snapshot\n const file = findFileInSnapshot(commits[0].filesSnapshot, file_path);\n\n if (!file) {\n // List available files\n const availableFiles = listAllFiles(commits[0].filesSnapshot);\n const fileList = availableFiles.slice(0, 10).join('\\n- ');\n const moreText =\n availableFiles.length > 10\n ? `\\n... and ${availableFiles.length - 10} more files`\n : '';\n\n return {\n content: [\n {\n type: 'text',\n text: `File not found: ${file_path}\\n\\nAvailable files:\\n- ${fileList}${moreText}\\n\\nTip: Use get_book_structure to see the full file tree.`,\n },\n ],\n isError: true,\n };\n }\n\n if (file.kind === 'folder') {\n return {\n content: [\n {\n type: 'text',\n text: `\"${file_path}\" is a folder, not a file. Please specify a file path.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!file.blobHash) {\n return {\n content: [\n {\n type: 'text',\n text: `File \"${file_path}\" has no content (empty file).`,\n },\n ],\n };\n }\n\n // 3. Download blob content\n const blobs = await client.downloadBlobs(book_token, [file.blobHash]);\n\n if (blobs.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'Could not retrieve file content. The blob may be missing.',\n },\n ],\n isError: true,\n };\n }\n\n const content = blobs[0].content;\n const wordInfo = file.wordCount ? ` (${file.wordCount} words)` : '';\n\n return {\n content: [\n {\n type: 'text',\n text: `# ${file.name}${wordInfo}\\n\\n${content}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/**\n * Find file in snapshot\n * Supports multiple path formats:\n * - Full path: /chapters/01.md\n * - Relative path: chapters/01.md\n * - Filename: 01.md\n * - Token: chapter-01\n */\nfunction findFileInSnapshot(\n files: FileSnapshot[],\n path: string\n): FileSnapshot | null {\n // Normalize path\n const normalizedPath = path.replace(/^\\//, '').toLowerCase();\n const pathParts = normalizedPath.split('/');\n\n // Recursive search\n function search(\n items: FileSnapshot[],\n remainingParts: string[]\n ): FileSnapshot | null {\n for (const item of items) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n // Full path match\n if (remainingParts.length === 1) {\n const target = remainingParts[0];\n if (\n itemNameLower === target ||\n itemTokenLower === target ||\n itemNameLower.replace(/\\.[^.]+$/, '') === target.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n }\n\n // Folder match, continue searching\n if (item.kind === 'folder' && item.children) {\n if (\n itemNameLower === remainingParts[0] ||\n itemTokenLower === remainingParts[0]\n ) {\n const result = search(item.children, remainingParts.slice(1));\n if (result) return result;\n }\n\n // Also try searching in subfolders directly\n const result = search(item.children, remainingParts);\n if (result) return result;\n }\n }\n\n return null;\n }\n\n // Try full path first\n let result = search(files, pathParts);\n if (result) return result;\n\n // Then try searching by filename only\n result = searchByName(files, pathParts[pathParts.length - 1]);\n return result;\n}\n\n/**\n * Search by filename only\n */\nfunction searchByName(\n files: FileSnapshot[],\n name: string\n): FileSnapshot | null {\n const nameLower = name.toLowerCase();\n\n for (const item of files) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (\n itemNameLower === nameLower ||\n itemTokenLower === nameLower ||\n itemNameLower.replace(/\\.[^.]+$/, '') === nameLower.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n\n if (item.kind === 'folder' && item.children) {\n const result = searchByName(item.children, name);\n if (result) return result;\n }\n }\n\n return null;\n}\n\n/**\n * List all file paths\n */\nfunction listAllFiles(files: FileSnapshot[], prefix = ''): string[] {\n const result: string[] = [];\n\n for (const item of files) {\n const path = prefix ? `${prefix}/${item.name}` : item.name;\n\n if (item.kind === 'file') {\n result.push(path);\n } else if (item.children) {\n result.push(...listAllFiles(item.children, path));\n }\n }\n\n return result;\n}\n","/**\n * Beta Reader Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient, Logger } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport type { FileSnapshot, ReaderTest, IndividualFeedback, AggregatedMetrics } from '../api/types.js';\n\n// Default no-op logger\nconst defaultLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\n/**\n * Register Beta Reader tools\n */\nexport function registerBetaReaderTools(\n server: McpServer,\n client: SlimaApiClient,\n logger: Logger = defaultLogger\n): void {\n // === list_personas ===\n server.tool(\n 'list_personas',\n 'List available virtual reader personas for beta reading feedback',\n {\n genre: z\n .string()\n .optional()\n .describe('Filter by genre (e.g., fantasy, romance, scifi)'),\n },\n async ({ genre }) => {\n try {\n const personas = await client.listPersonas(genre);\n\n if (personas.length === 0) {\n const genreText = genre ? ` for genre \"${genre}\"` : '';\n return {\n content: [\n {\n type: 'text',\n text: `No personas found${genreText}.`,\n },\n ],\n };\n }\n\n const formatted = personas\n .map((p) => {\n const name = p.displayLabels?.zh || p.displayLabels?.en || p.slug;\n const tags = p.genreTags?.join(', ') || 'General';\n const demo = p.demographics || {};\n const gender = demo.gender || 'N/A';\n const age = demo.ageRange || 'N/A';\n\n return `- **${name}** (\\`${p.token}\\`)\n Gender: ${gender} | Age: ${age}\n Genres: ${tags}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Available Personas (${personas.length}):\\n\\n${formatted}\\n\\n**Tip**: Use the persona token with \\`analyze_chapter\\` to get feedback from a specific reader.`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === analyze_chapter ===\n server.tool(\n 'analyze_chapter',\n 'Get AI Beta Reader feedback on a chapter from a specific reader persona. This may take a moment to complete.',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n file_path: z.string().describe('Chapter file path (e.g., /chapters/01.md)'),\n persona_token: z\n .string()\n .describe('Persona token (e.g., psn_xxx). Use list_personas to find available personas.'),\n },\n async ({ book_token, file_path, persona_token }) => {\n try {\n // 1. Get latest commit and chapter content\n const commits = await client.listCommits(book_token, 1);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n isError: true,\n };\n }\n\n // 2. Find file\n const file = findFileInSnapshot(commits[0].filesSnapshot, file_path);\n\n if (!file) {\n return {\n content: [\n {\n type: 'text',\n text: `File not found: ${file_path}. Use get_book_structure to see available files.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!file.blobHash) {\n return {\n content: [\n {\n type: 'text',\n text: `File \"${file_path}\" has no content (empty file).`,\n },\n ],\n isError: true,\n };\n }\n\n // 3. Download content\n const blobs = await client.downloadBlobs(book_token, [file.blobHash]);\n\n if (blobs.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'Could not retrieve file content.',\n },\n ],\n isError: true,\n };\n }\n\n const content = blobs[0].content;\n\n // 4. Create Reader Test\n logger.info(`Creating reader test for ${file_path} with persona ${persona_token}`);\n\n const test = await client.createReaderTest(book_token, {\n reportType: 'chapter',\n personaTokens: [persona_token],\n commitToken: commits[0].token,\n scopeConfig: { chapters: [file_path] },\n content: content,\n });\n\n // 5. Poll for completion (max 90 seconds)\n const result = await pollForCompletion(\n () => client.getReaderTestProgress(book_token, test.token),\n 90000,\n logger\n );\n\n if (result.status === 'failed') {\n return {\n content: [\n {\n type: 'text',\n text: 'Analysis failed. Please try again later.',\n },\n ],\n isError: true,\n };\n }\n\n if (result.status !== 'completed') {\n return {\n content: [\n {\n type: 'text',\n text: `Analysis is still in progress. You can check the status later.\\n\\nTest ID: ${test.token}`,\n },\n ],\n };\n }\n\n // 6. Get full result\n const fullResult = await client.getReaderTest(book_token, test.token);\n\n // 7. Format feedback\n const feedback = formatBetaReaderFeedback(fullResult, file.name);\n\n return {\n content: [{ type: 'text', text: feedback }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/**\n * Poll for completion\n */\nasync function pollForCompletion(\n checkFn: () => Promise<{ status: string; progress?: number }>,\n timeoutMs: number,\n logger: Logger\n): Promise<{ status: string }> {\n const startTime = Date.now();\n const pollInterval = 3000; // 3 seconds\n\n while (Date.now() - startTime < timeoutMs) {\n const result = await checkFn();\n logger.debug(`Poll status: ${result.status}, progress: ${result.progress || 'N/A'}`);\n\n if (result.status === 'completed' || result.status === 'failed') {\n return result;\n }\n\n await sleep(pollInterval);\n }\n\n return { status: 'timeout' };\n}\n\n/**\n * Sleep\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Format Beta Reader feedback\n */\nfunction formatBetaReaderFeedback(test: ReaderTest, fileName: string): string {\n const feedbacks = test.individualFeedbacks || [];\n const metrics = test.aggregatedMetrics;\n\n if (feedbacks.length === 0 && !metrics) {\n return 'No feedback available.';\n }\n\n let output = `# Beta Reader Feedback: ${fileName}\\n\\n`;\n\n // Aggregated metrics summary\n if (metrics) {\n output += formatAggregatedMetrics(metrics);\n }\n\n // Individual persona feedback\n if (feedbacks.length > 0) {\n output += `\\n---\\n\\n## Individual Reader Feedback\\n\\n`;\n output += feedbacks.map(formatIndividualFeedback).join('\\n---\\n\\n');\n }\n\n return output;\n}\n\n/**\n * Format aggregated metrics\n */\nfunction formatAggregatedMetrics(metrics: AggregatedMetrics): string {\n let output = `## Summary Metrics\\n\\n`;\n\n // Overall\n if (metrics.overall) {\n const o = metrics.overall;\n output += `### Overall Scores\\n`;\n if (o.avgContinueReading) output += `- Continue Reading Intent: ${o.avgContinueReading.toFixed(1)}/10\\n`;\n if (o.avgRecommendation) output += `- Recommendation Score: ${o.avgRecommendation.toFixed(1)}/10\\n`;\n if (o.avgWantMore) output += `- Want More: ${o.avgWantMore.toFixed(1)}/10\\n`;\n if (o.dnfRisk) output += `- DNF Risk: ${o.dnfRisk}\\n`;\n output += '\\n';\n }\n\n // Kindle Rating\n if (metrics.kindleRating) {\n output += `### Predicted Kindle Rating\\n`;\n output += `- Average Score: ⭐ ${metrics.kindleRating.avgScore.toFixed(1)}/5\\n`;\n if (metrics.kindleRating.individualScores) {\n const scores = metrics.kindleRating.individualScores\n .map(s => `${s.personaName || 'Reader'}: ${s.score}/5 (${s.confidence})`)\n .join(', ');\n output += `- Individual Scores: ${scores}\\n`;\n }\n output += '\\n';\n }\n\n // Characters\n if (metrics.characters) {\n const c = metrics.characters;\n output += `### Character Evaluation\\n`;\n if (c.avgProtagonistLikability) output += `- Protagonist Likability: ${c.avgProtagonistLikability.toFixed(1)}/10\\n`;\n if (c.avgMotivationClarity) output += `- Motivation Clarity: ${c.avgMotivationClarity.toFixed(1)}/10\\n`;\n if (c.avgDialogueNaturalness) output += `- Dialogue Naturalness: ${c.avgDialogueNaturalness.toFixed(1)}/10\\n`;\n output += '\\n';\n }\n\n // Pacing\n if (metrics.pacing) {\n const p = metrics.pacing;\n output += `### Pacing Evaluation\\n`;\n if (p.avgOverall) output += `- Overall Pacing: ${p.avgOverall.toFixed(1)}/10\\n`;\n if (p.consensus) output += `- Consensus: ${p.consensus}\\n`;\n if (p.commonDnfTriggers && p.commonDnfTriggers.length > 0) {\n output += `- Common DNF Triggers:\\n`;\n p.commonDnfTriggers.slice(0, 3).forEach(t => {\n output += ` - ${t.trigger} (${t.count}x)\\n`;\n });\n }\n output += '\\n';\n }\n\n // Market\n if (metrics.market) {\n const m = metrics.market;\n output += `### Market Positioning\\n`;\n if (m.avgWordOfMouth) output += `- Word of Mouth Potential: ${m.avgWordOfMouth.toFixed(1)}/10\\n`;\n if (m.topComparableBooks && m.topComparableBooks.length > 0) {\n const books = m.topComparableBooks.slice(0, 3).map(b => b.title).join(', ');\n output += `- Comparable Books: ${books}\\n`;\n }\n output += '\\n';\n }\n\n return output;\n}\n\n/**\n * Format individual feedback\n */\nfunction formatIndividualFeedback(feedback: IndividualFeedback): string {\n const name = feedback.personaName || feedback.personaToken;\n let output = `### ${name}\\n\\n`;\n\n // Overall scores\n if (feedback.overall) {\n const o = feedback.overall;\n if (o.continueReading) output += `**Continue Reading Intent**: ${o.continueReading}/10\\n`;\n if (o.recommendation) output += `**Recommendation**: ${o.recommendation}/10\\n`;\n if (o.dnfRisk) output += `**DNF Risk**: ${o.dnfRisk}\\n`;\n output += '\\n';\n }\n\n // Kindle Rating\n if (feedback.kindleRating) {\n output += `**Kindle Rating**: ⭐ ${feedback.kindleRating.score}/5`;\n if (feedback.kindleRating.rationale) {\n output += ` - ${feedback.kindleRating.rationale}`;\n }\n output += '\\n\\n';\n }\n\n // Detailed Feedback\n if (feedback.detailedFeedback) {\n const d = feedback.detailedFeedback;\n\n if (d.whatWorked) {\n output += `**What Worked**:\\n${d.whatWorked}\\n\\n`;\n }\n\n if (d.whatDidntWork) {\n output += `**What Didn't Work**:\\n${d.whatDidntWork}\\n\\n`;\n }\n\n if (d.strongestElement) {\n output += `**Strongest Element**: ${d.strongestElement}\\n`;\n }\n\n if (d.weakestElement) {\n output += `**Weakest Element**: ${d.weakestElement}\\n`;\n }\n\n if (d.wouldYouBuy) {\n output += `**Would You Buy**: ${d.wouldYouBuy}\\n`;\n }\n\n if (d.specificSuggestions && d.specificSuggestions.length > 0) {\n output += `\\n**Specific Suggestions**:\\n`;\n d.specificSuggestions.forEach((s, i) => {\n output += `${i + 1}. ${s}\\n`;\n });\n }\n\n if (d.finalThoughts) {\n output += `\\n**Final Thoughts**:\\n${d.finalThoughts}\\n`;\n }\n }\n\n // Emotional Journey\n if (feedback.emotionalJourney) {\n const e = feedback.emotionalJourney;\n if (e.emotionalArc) {\n output += `\\n**Emotional Arc**: ${e.emotionalArc}\\n`;\n }\n if (e.peakMoments && e.peakMoments.length > 0) {\n const moments = e.peakMoments.map(m =>\n typeof m === 'string' ? m : (m as { moment?: string; description?: string }).moment || (m as { moment?: string; description?: string }).description || JSON.stringify(m)\n );\n output += `**Peak Moments**: ${moments.join(', ')}\\n`;\n }\n if (e.overallMood) {\n output += `**Overall Mood**: ${e.overallMood}\\n`;\n }\n }\n\n return output;\n}\n\n/**\n * Find file in snapshot\n */\nfunction findFileInSnapshot(\n files: FileSnapshot[],\n path: string\n): FileSnapshot | null {\n const normalizedPath = path.replace(/^\\//, '').toLowerCase();\n const pathParts = normalizedPath.split('/');\n\n function search(\n items: FileSnapshot[],\n remainingParts: string[]\n ): FileSnapshot | null {\n for (const item of items) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (remainingParts.length === 1) {\n const target = remainingParts[0];\n if (\n itemNameLower === target ||\n itemTokenLower === target ||\n itemNameLower.replace(/\\.[^.]+$/, '') === target.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n }\n\n if (item.kind === 'folder' && item.children) {\n if (\n itemNameLower === remainingParts[0] ||\n itemTokenLower === remainingParts[0]\n ) {\n const result = search(item.children, remainingParts.slice(1));\n if (result) return result;\n }\n\n const result = search(item.children, remainingParts);\n if (result) return result;\n }\n }\n\n return null;\n }\n\n let result = search(files, pathParts);\n if (result) return result;\n\n result = searchByName(files, pathParts[pathParts.length - 1]);\n return result;\n}\n\nfunction searchByName(\n files: FileSnapshot[],\n name: string\n): FileSnapshot | null {\n const nameLower = name.toLowerCase();\n\n for (const item of files) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (\n itemNameLower === nameLower ||\n itemTokenLower === nameLower ||\n itemNameLower.replace(/\\.[^.]+$/, '') === nameLower.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n\n if (item.kind === 'folder' && item.children) {\n const result = searchByName(item.children, name);\n if (result) return result;\n }\n }\n\n return null;\n}\n","/**\n * MCP File Operations Tools\n *\n * Provides tools for AI to read, write, create, delete, and search files in books.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\n/**\n * Register file operation tools\n */\nexport function registerFileTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === read_file ===\n server.tool(\n 'read_file',\n 'Read the content of any file in a book by its path',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"characters/protagonist.md\" or \"chapter-01.md\")'),\n },\n async ({ book_token, path }) => {\n try {\n const result = await client.readFile(book_token, path);\n\n const fileInfo = [\n `# ${result.file.name}`,\n '',\n `**Path:** ${result.file.path}`,\n `**Words:** ${result.file.wordCount}`,\n `**Type:** ${result.file.fileType || 'unknown'}`,\n '',\n '---',\n '',\n result.content || '(empty file)',\n ].join('\\n');\n\n return {\n content: [{ type: 'text', text: fileInfo }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === write_file ===\n server.tool(\n 'write_file',\n 'Replace the entire content of an existing file (creates a new commit)',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"characters/protagonist.md\")'),\n content: z.string().describe('New content to write'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.updateFile(book_token, {\n path,\n content,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `File updated successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === edit_file ===\n server.tool(\n 'edit_file',\n 'Edit a file by replacing a specific text segment (search and replace). Use this for precise edits instead of rewriting the entire file.',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"characters/protagonist.md\")'),\n old_string: z.string().describe('The exact text to find and replace (must match exactly, including whitespace)'),\n new_string: z.string().describe('The new text to replace it with'),\n replace_all: z.boolean().optional().describe('Replace all occurrences (default: false, replaces only first match)'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, old_string, new_string, replace_all, commit_message }) => {\n try {\n // 1. Read current content\n const fileData = await client.readFile(book_token, path);\n const currentContent = fileData.content || '';\n\n // 2. Check if old_string exists\n if (!currentContent.includes(old_string)) {\n return {\n content: [\n {\n type: 'text',\n text: `❌ Edit failed: Could not find the specified text in the file.\\n\\n**Looking for:**\\n\\`\\`\\`\\n${old_string.slice(0, 200)}${old_string.length > 200 ? '...' : ''}\\n\\`\\`\\`\\n\\n**Tip:** Make sure the text matches exactly, including whitespace and line breaks.`,\n },\n ],\n isError: true,\n };\n }\n\n // 3. Check for uniqueness (if not replace_all)\n if (!replace_all) {\n const occurrences = currentContent.split(old_string).length - 1;\n if (occurrences > 1) {\n return {\n content: [\n {\n type: 'text',\n text: `❌ Edit failed: Found ${occurrences} occurrences of the text. Please provide a more specific/longer text segment to ensure a unique match, or set replace_all: true to replace all occurrences.`,\n },\n ],\n isError: true,\n };\n }\n }\n\n // 4. Perform replacement\n const newContent = replace_all\n ? currentContent.split(old_string).join(new_string)\n : currentContent.replace(old_string, new_string);\n\n // 5. Write back\n const result = await client.updateFile(book_token, {\n path,\n content: newContent,\n commitMessage: commit_message || `Edit ${path}`,\n });\n\n const replacementCount = replace_all\n ? currentContent.split(old_string).length - 1\n : 1;\n\n return {\n content: [\n {\n type: 'text',\n text: `✅ File edited successfully.\\n\\n**Replacements:** ${replacementCount}\\n**Commit:** ${result.commit.token}\\n**Message:** ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === create_file ===\n server.tool(\n 'create_file',\n 'Create a new file in a book (creates a new commit)',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path including parent folders (e.g., \"characters/new-character.md\")'),\n content: z.string().optional().describe('Initial content (optional, defaults to empty)'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.createFile(book_token, {\n path,\n content: content || '',\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `File created successfully.\\n\\nFile Token: ${result.fileToken}\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === delete_file ===\n server.tool(\n 'delete_file',\n 'Delete a file from a book (creates a new commit)',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path to delete (e.g., \"old-notes.md\")'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, commit_message }) => {\n try {\n const result = await client.deleteFile(book_token, {\n path,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `File deleted successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === append_to_file ===\n server.tool(\n 'append_to_file',\n 'Append content to the end of an existing file (creates a new commit)',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"notes.md\")'),\n content: z.string().describe('Content to append'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.appendToFile(book_token, {\n path,\n content,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `Content appended successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === search_content ===\n server.tool(\n 'search_content',\n 'Search for content across all files in a book',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n query: z.string().describe('Search query (e.g., \"blue eyes\" or \"protagonist name\")'),\n file_types: z.array(z.string()).optional().describe('Filter by file types (e.g., [\"markdown\", \"txt\"])'),\n limit: z.number().optional().describe('Maximum number of matches to return (default: 20)'),\n },\n async ({ book_token, query, file_types, limit }) => {\n try {\n const result = await client.searchFiles(book_token, {\n query,\n fileTypes: file_types,\n limit,\n });\n\n if (result.matches.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: `No matches found for \"${query}\".`,\n },\n ],\n };\n }\n\n const output: string[] = [\n `# Search Results for \"${query}\"`,\n '',\n `Found ${result.matches.length} file(s) with matches:`,\n '',\n ];\n\n for (const match of result.matches) {\n output.push(`## ${match.file.path}`);\n output.push(`Words: ${match.file.wordCount} | Matches: ${match.matchCount}`);\n output.push('');\n\n for (const snippet of match.snippets) {\n output.push(`> ${snippet.text}`);\n output.push('');\n }\n }\n\n return {\n content: [{ type: 'text', text: output.join('\\n') }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * Logger 工具\n *\n * 重要:MCP STDIO 模式下,stdout 是協議通道,\n * 所有日誌必須輸出到 stderr\n */\n\nconst isDebug = process.env.DEBUG === 'true' || process.env.DEBUG === '1';\n\nexport const logger = {\n info: (message: string, ...args: unknown[]) => {\n console.error(`[INFO] ${message}`, ...args);\n },\n\n warn: (message: string, ...args: unknown[]) => {\n console.error(`[WARN] ${message}`, ...args);\n },\n\n error: (message: string, error?: unknown) => {\n if (error instanceof Error) {\n console.error(`[ERROR] ${message}:`, error.message);\n if (isDebug && error.stack) {\n console.error(error.stack);\n }\n } else if (error !== undefined) {\n console.error(`[ERROR] ${message}:`, error);\n } else {\n console.error(`[ERROR] ${message}`);\n }\n },\n\n debug: (message: string, ...args: unknown[]) => {\n if (isDebug) {\n console.error(`[DEBUG] ${message}`, ...args);\n }\n },\n};\n","/**\n * Token Storage Utility (CLI)\n *\n * Stores API Token in user's home directory\n * ~/.slima/credentials.json\n */\n\nimport { promises as fs } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst SLIMA_DIR = '.slima';\nconst CREDENTIALS_FILE = 'credentials.json';\n\ninterface Credentials {\n apiToken: string;\n createdAt: string;\n baseUrl?: string;\n}\n\nfunction getCredentialsPath(): string {\n return join(homedir(), SLIMA_DIR, CREDENTIALS_FILE);\n}\n\nfunction getSlimaDir(): string {\n return join(homedir(), SLIMA_DIR);\n}\n\n/**\n * Save API Token\n */\nexport async function saveToken(apiToken: string, baseUrl?: string): Promise<void> {\n const slimaDir = getSlimaDir();\n\n // Ensure directory exists\n await fs.mkdir(slimaDir, { recursive: true });\n\n const credentials: Credentials = {\n apiToken,\n createdAt: new Date().toISOString(),\n ...(baseUrl && { baseUrl }),\n };\n\n const credentialsPath = getCredentialsPath();\n await fs.writeFile(credentialsPath, JSON.stringify(credentials, null, 2), 'utf-8');\n\n // Set file permission to 600 (owner only)\n await fs.chmod(credentialsPath, 0o600);\n}\n\n/**\n * Load API Token\n */\nexport async function loadToken(): Promise<Credentials | null> {\n try {\n const credentialsPath = getCredentialsPath();\n const content = await fs.readFile(credentialsPath, 'utf-8');\n return JSON.parse(content) as Credentials;\n } catch {\n return null;\n }\n}\n\n/**\n * Delete API Token\n */\nexport async function deleteToken(): Promise<void> {\n try {\n const credentialsPath = getCredentialsPath();\n await fs.unlink(credentialsPath);\n } catch {\n // File doesn't exist, ignore\n }\n}\n\n/**\n * Check if authenticated\n */\nexport async function isAuthenticated(): Promise<boolean> {\n const credentials = await loadToken();\n return credentials !== null && !!credentials.apiToken;\n}\n\n/**\n * Get credentials file path (for display)\n */\nexport function getCredentialsFilePath(): string {\n return getCredentialsPath();\n}\n","/**\n * CLI Auth Command\n *\n * 實作類似 Claude Code / GitHub CLI 的認證流程:\n * 1. 使用者執行 `slima-mcp auth`\n * 2. 開啟瀏覽器到 Slima 認證頁面\n * 3. 使用者登入並授權\n * 4. 本地伺服器接收 callback 並儲存 token\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport { randomBytes } from 'node:crypto';\nimport { saveToken, loadToken, deleteToken, getCredentialsFilePath } from './token-storage.js';\n\n// 認證使用 API 伺服器(處理 OAuth 和 session)\nconst DEFAULT_API_URL = 'https://api.slima.ai';\nconst CALLBACK_PORT_START = 8765;\nconst CALLBACK_PORT_END = 8775;\n\n/**\n * 開啟瀏覽器\n */\nasync function openBrowser(url: string): Promise<void> {\n const { exec } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execAsync = promisify(exec);\n\n const platform = process.platform;\n\n try {\n if (platform === 'darwin') {\n await execAsync(`open \"${url}\"`);\n } else if (platform === 'win32') {\n await execAsync(`start \"\" \"${url}\"`);\n } else {\n // Linux / WSL\n // 嘗試多個可能的命令\n const commands = ['xdg-open', 'sensible-browser', 'wslview'];\n for (const cmd of commands) {\n try {\n await execAsync(`${cmd} \"${url}\"`);\n return;\n } catch {\n // 繼續嘗試下一個\n }\n }\n // 如果都失敗,顯示 URL 讓使用者手動開啟\n console.log(`\\nPlease open this URL in your browser:\\n${url}\\n`);\n }\n } catch {\n console.log(`\\nPlease open this URL in your browser:\\n${url}\\n`);\n }\n}\n\n/**\n * 尋找可用的 port\n */\nasync function findAvailablePort(): Promise<number> {\n for (let port = CALLBACK_PORT_START; port <= CALLBACK_PORT_END; port++) {\n const available = await isPortAvailable(port);\n if (available) {\n return port;\n }\n }\n throw new Error(`No available ports between ${CALLBACK_PORT_START} and ${CALLBACK_PORT_END}`);\n}\n\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once('error', () => resolve(false));\n server.once('listening', () => {\n server.close(() => resolve(true));\n });\n server.listen(port, '127.0.0.1');\n });\n}\n\n/**\n * 產生隨機 state(防止 CSRF)\n */\nfunction generateState(): string {\n return randomBytes(16).toString('hex');\n}\n\n/**\n * 成功頁面 HTML\n * Design follows UIUX_SPEC.md: minimalist, grayscale, Inter font\n */\nfunction getSuccessHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Welcome to Slima</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n background: #FBFBFA;\n color: #1D1D1F;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .container {\n text-align: center;\n padding: 48px;\n max-width: 420px;\n }\n .logo {\n width: 48px;\n height: 48px;\n margin-bottom: 32px;\n opacity: 0.9;\n }\n .success-icon {\n width: 64px;\n height: 64px;\n margin-bottom: 24px;\n border-radius: 50%;\n background: #1D1D1F;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n margin-right: auto;\n }\n .success-icon svg {\n width: 32px;\n height: 32px;\n stroke: #FBFBFA;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n letter-spacing: -0.02em;\n margin-bottom: 12px;\n color: #1D1D1F;\n }\n .subtitle {\n font-size: 15px;\n color: #6E6E73;\n line-height: 1.5;\n margin-bottom: 8px;\n }\n .hint {\n font-size: 13px;\n color: #86868B;\n margin-top: 24px;\n }\n .divider {\n width: 40px;\n height: 1px;\n background: #E5E5E5;\n margin: 32px auto;\n }\n .footer {\n font-size: 12px;\n color: #86868B;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <img src=\"https://app.slima.ai/icons/slima-black.svg\" alt=\"Slima\" class=\"logo\">\n <div class=\"success-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.5 12.75l6 6 9-13.5\" />\n </svg>\n </div>\n <h1>Welcome to Slima</h1>\n <p class=\"subtitle\">Authentication successful. Your CLI is now connected.</p>\n <p class=\"hint\">You can close this window and return to your terminal.</p>\n <div class=\"divider\"></div>\n <p class=\"footer\">Slima MCP is ready to use</p>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * 錯誤頁面 HTML\n */\n/**\n * HTML escape to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n };\n return text.replace(/[&<>\"']/g, (char) => map[char]);\n}\n\n/**\n * 錯誤頁面 HTML\n * Design follows UIUX_SPEC.md: minimalist, grayscale, Inter font\n */\nfunction getErrorHtml(message: string): string {\n const safeMessage = escapeHtml(message);\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Slima - Authentication Error</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n background: #FBFBFA;\n color: #1D1D1F;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .container {\n text-align: center;\n padding: 48px;\n max-width: 420px;\n }\n .logo {\n width: 48px;\n height: 48px;\n margin-bottom: 32px;\n opacity: 0.9;\n }\n .error-icon {\n width: 64px;\n height: 64px;\n margin-bottom: 24px;\n border-radius: 50%;\n background: #1D1D1F;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n margin-right: auto;\n }\n .error-icon svg {\n width: 32px;\n height: 32px;\n stroke: #FBFBFA;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n letter-spacing: -0.02em;\n margin-bottom: 12px;\n color: #1D1D1F;\n }\n .message {\n font-size: 15px;\n color: #6E6E73;\n line-height: 1.5;\n margin-bottom: 8px;\n }\n .hint {\n font-size: 13px;\n color: #86868B;\n margin-top: 24px;\n }\n .divider {\n width: 40px;\n height: 1px;\n background: #E5E5E5;\n margin: 32px auto;\n }\n .retry-link {\n display: inline-block;\n font-size: 14px;\n font-weight: 500;\n color: #1D1D1F;\n text-decoration: none;\n padding: 10px 20px;\n border: 1px solid #E5E5E5;\n border-radius: 8px;\n transition: all 0.2s ease;\n }\n .retry-link:hover {\n background: #F5F5F5;\n border-color: #D5D5D5;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <img src=\"https://app.slima.ai/icons/slima-black.svg\" alt=\"Slima\" class=\"logo\">\n <div class=\"error-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </div>\n <h1>Something went wrong</h1>\n <p class=\"message\">${safeMessage}</p>\n <p class=\"hint\">Please close this window and try again from your terminal.</p>\n <div class=\"divider\"></div>\n <a href=\"javascript:window.close()\" class=\"retry-link\">Close Window</a>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * 執行認證流程\n * @param options.baseUrl - API base URL (default: https://api.slima.ai)\n * @param options.force - Force re-authentication even if already authenticated\n */\nexport async function runAuth(options: { baseUrl?: string; force?: boolean } = {}): Promise<void> {\n const baseUrl = options.baseUrl || process.env.SLIMA_API_URL || DEFAULT_API_URL;\n const forceAuth = options.force || false;\n\n console.log('\\n🔐 Slima MCP Authentication\\n');\n\n // 檢查是否已經認證\n const existingCredentials = await loadToken();\n if (existingCredentials && !forceAuth) {\n console.log('You are already authenticated.');\n console.log(`Token stored at: ${getCredentialsFilePath()}`);\n console.log('\\nTo re-authenticate with a different account, run: slima-mcp auth --force\\n');\n return;\n }\n\n // 如果強制認證,先刪除舊 token\n if (existingCredentials && forceAuth) {\n await deleteToken();\n console.log('Clearing existing credentials for re-authentication...\\n');\n }\n\n const state = generateState();\n const port = await findAvailablePort();\n\n console.log('Starting local server for authentication callback...');\n\n // 建立本地伺服器接收 callback\n const tokenPromise = new Promise<string>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`);\n\n if (url.pathname === '/callback') {\n const token = url.searchParams.get('token');\n const returnedState = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n\n if (error) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml(error));\n server.close();\n reject(new Error(error));\n return;\n }\n\n if (returnedState !== state) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml('Invalid state parameter. Please try again.'));\n server.close();\n reject(new Error('State mismatch'));\n return;\n }\n\n if (!token) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml('No token received. Please try again.'));\n server.close();\n reject(new Error('No token received'));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getSuccessHtml());\n server.close();\n resolve(token);\n } else {\n res.writeHead(404);\n res.end('Not found');\n }\n });\n\n server.listen(port, '127.0.0.1', () => {\n console.log(`Listening on http://127.0.0.1:${port}`);\n });\n\n // 30 秒超時\n setTimeout(() => {\n server.close();\n reject(new Error('Authentication timed out. Please try again.'));\n }, 30000);\n });\n\n // 建立認證 URL(使用 API endpoint)\n // 加入 prompt=login 強制重新登入(清除 Rails session)\n const promptParam = forceAuth ? '&prompt=login' : '';\n const authUrl = `${baseUrl}/api/v1/auth/cli?state=${state}&callback_port=${port}${promptParam}`;\n\n console.log('\\nOpening browser for authentication...');\n await openBrowser(authUrl);\n\n console.log('\\nWaiting for authentication...');\n console.log('(Press Ctrl+C to cancel)\\n');\n\n try {\n const token = await tokenPromise;\n await saveToken(token, baseUrl !== DEFAULT_API_URL ? baseUrl : undefined);\n\n console.log('\\n✅ Authentication successful!');\n console.log(`Token saved to: ${getCredentialsFilePath()}`);\n console.log('\\nYou can now use Slima MCP with any MCP-compatible AI tool.\\n');\n } catch (error) {\n console.error('\\n❌ Authentication failed:', error instanceof Error ? error.message : error);\n process.exit(1);\n }\n}\n\n/**\n * 登出(刪除 token)\n */\nexport async function runLogout(): Promise<void> {\n console.log('\\n🔓 Slima MCP Logout\\n');\n\n const credentials = await loadToken();\n if (!credentials) {\n console.log('You are not authenticated.\\n');\n return;\n }\n\n await deleteToken();\n console.log('✅ Successfully logged out.');\n console.log(`Removed credentials from: ${getCredentialsFilePath()}\\n`);\n}\n\n/**\n * 顯示目前狀態\n */\nexport async function runStatus(): Promise<void> {\n console.log('\\n📊 Slima MCP Status\\n');\n\n const credentials = await loadToken();\n if (!credentials) {\n console.log('Status: Not authenticated');\n console.log('\\nRun `slima-mcp auth` to authenticate.\\n');\n return;\n }\n\n console.log('Status: Authenticated');\n console.log(`Token: ${credentials.apiToken.substring(0, 15)}...`);\n console.log(`Authenticated at: ${credentials.createdAt}`);\n if (credentials.baseUrl) {\n console.log(`API URL: ${credentials.baseUrl}`);\n }\n console.log(`Credentials file: ${getCredentialsFilePath()}\\n`);\n}\n"],"mappings":"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","names":["z","z","result","z","defaultLogger","logger","z","findFileInSnapshot","result","searchByName","z","z"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slima-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "MCP Server for Slima - AI Writing IDE for Novel Authors. Works with Claude, ChatGPT, Gemini, Cursor, and any MCP-compatible AI tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsup",
|
|
18
|
+
"build:cli": "tsup",
|
|
19
|
+
"build:worker": "tsup --config tsup.worker.config.ts",
|
|
20
|
+
"build:all": "npm run build:cli && npm run build:worker",
|
|
18
21
|
"dev": "tsup --watch",
|
|
22
|
+
"dev:worker": "wrangler dev",
|
|
23
|
+
"deploy:worker": "npm run build:worker && wrangler deploy",
|
|
24
|
+
"deploy:worker:preview": "npm run build:worker && wrangler deploy --env preview",
|
|
19
25
|
"test": "vitest",
|
|
20
26
|
"test:run": "vitest run",
|
|
21
27
|
"test:coverage": "vitest run --coverage",
|
|
@@ -52,13 +58,16 @@
|
|
|
52
58
|
},
|
|
53
59
|
"dependencies": {
|
|
54
60
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
61
|
+
"hono": "^4.0.0",
|
|
55
62
|
"zod": "^3.23.0"
|
|
56
63
|
},
|
|
57
64
|
"devDependencies": {
|
|
65
|
+
"@cloudflare/workers-types": "^4.0.0",
|
|
58
66
|
"@types/node": "^20.0.0",
|
|
59
67
|
"@vitest/coverage-v8": "^2.1.9",
|
|
60
68
|
"tsup": "^8.0.0",
|
|
61
69
|
"typescript": "^5.4.0",
|
|
62
|
-
"vitest": "^2.0.0"
|
|
70
|
+
"vitest": "^2.0.0",
|
|
71
|
+
"wrangler": "^3.0.0"
|
|
63
72
|
}
|
|
64
73
|
}
|