subwrench-mcp 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/bin/server.js +127 -0
  2. package/package.json +13 -4
  3. package/README.md +0 -3
package/bin/server.js ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import * as core from "subwrench-core";
6
+
7
+ const server = new McpServer({ name: "subwrench-mcp", version: "0.1.0" });
8
+
9
+ const ok = (text) => ({ content: [{ type: "text", text }] });
10
+ const fail = (e) => ({ content: [{ type: "text", text: `Error: ${e.message}` }], isError: true });
11
+
12
+ const wrap = (fn) => async (args) => {
13
+ try {
14
+ return ok(await fn(args));
15
+ } catch (e) {
16
+ return fail(e);
17
+ }
18
+ };
19
+
20
+ server.tool(
21
+ "subtitle_convert",
22
+ "Convert subtitles between srt/vtt/sbv/ass formats. Pass raw subtitle content (not a file path).",
23
+ {
24
+ content: z.string().describe("Raw subtitle file content"),
25
+ to: z.enum(["srt", "vtt", "sbv", "ass"]).describe("Target format"),
26
+ from: z.enum(["srt", "vtt", "sbv", "ass"]).optional().describe("Source format; auto-detected if omitted"),
27
+ },
28
+ wrap(({ content, from, to }) => core.subtitles.convert(content, from, to))
29
+ );
30
+
31
+ server.tool(
32
+ "subtitle_shift",
33
+ "Shift all subtitle cues by a millisecond offset (positive = later, negative = earlier).",
34
+ {
35
+ content: z.string().describe("Raw subtitle file content"),
36
+ ms: z.number().int().describe("Offset in milliseconds"),
37
+ },
38
+ wrap(({ content, ms }) => core.subtitles.shift(content, ms))
39
+ );
40
+
41
+ server.tool(
42
+ "subtitle_merge",
43
+ "Merge two subtitle files (e.g. translated + original) into one, sorted by start time.",
44
+ {
45
+ a: z.string().describe("First subtitle content"),
46
+ b: z.string().describe("Second subtitle content"),
47
+ output_format: z.enum(["srt", "vtt", "sbv", "ass"]).optional(),
48
+ },
49
+ wrap(({ a, b, output_format }) => core.subtitles.merge(a, b, null, null, output_format || "srt"))
50
+ );
51
+
52
+ server.tool(
53
+ "subtitle_stats",
54
+ "Get stats for subtitle content: format, cue count, duration, character count.",
55
+ { content: z.string() },
56
+ wrap(({ content }) => JSON.stringify(core.subtitles.stats(content), null, 2))
57
+ );
58
+
59
+ server.tool(
60
+ "text_count",
61
+ "Count words/characters/sentences/paragraphs. Handles CJK (Chinese/Japanese/Korean) correctly: CJK chars count as words.",
62
+ { text: z.string() },
63
+ wrap(({ text }) => JSON.stringify(core.text.countStats(text), null, 2))
64
+ );
65
+
66
+ server.tool(
67
+ "estimate_tokens",
68
+ "Estimate LLM token count for text before sending to gpt/claude/gemini. Useful for prompt budgeting.",
69
+ {
70
+ text: z.string(),
71
+ model: z.enum(["gpt", "claude", "gemini"]).default("gpt"),
72
+ },
73
+ wrap(({ text, model }) => `~${core.text.estimateTokens(text, model)} tokens (${model})`)
74
+ );
75
+
76
+ server.tool(
77
+ "case_transform",
78
+ "Transform text case: upper, lower, title, sentence, camel, pascal, snake, kebab, constant.",
79
+ {
80
+ text: z.string(),
81
+ to: z.enum(["upper", "lower", "title", "sentence", "camel", "pascal", "snake", "kebab", "constant"]),
82
+ },
83
+ wrap(({ text, to }) => core.text.caseTransform(text, to))
84
+ );
85
+
86
+ server.tool(
87
+ "json_tool",
88
+ "Validate / format / minify / sort-keys / path-get on JSON strings.",
89
+ {
90
+ json: z.string().describe("Raw JSON string"),
91
+ op: z.enum(["validate", "format", "minify", "sort", "get", "stats"]),
92
+ indent: z.number().int().min(0).max(8).default(2),
93
+ path: z.string().optional().describe("Dot path for 'get', e.g. a.b[1].c"),
94
+ },
95
+ wrap(({ json, op, indent, path }) => {
96
+ switch (op) {
97
+ case "validate": return JSON.stringify(core.json.validate(json));
98
+ case "format": return core.json.format(json, indent);
99
+ case "minify": return core.json.minify(json);
100
+ case "sort": return core.json.sortKeys(json);
101
+ case "get": return JSON.stringify(core.json.get(json, path ?? ""));
102
+ case "stats": return JSON.stringify(core.json.stats(json));
103
+ }
104
+ })
105
+ );
106
+
107
+ server.tool(
108
+ "hash_tool",
109
+ "Generate uuid v4, sha256/sha1/md5, base64 encode/decode, hmac.",
110
+ {
111
+ op: z.enum(["uuid", "sha256", "sha1", "md5", "b64e", "b64d"]),
112
+ input: z.string().optional(),
113
+ n: z.number().int().min(1).max(100).default(1),
114
+ },
115
+ wrap(({ op, input = "", n }) => {
116
+ switch (op) {
117
+ case "uuid": return core.hash.uuid(n).join("\n");
118
+ case "sha256": return core.hash.sha256(input);
119
+ case "sha1": return core.hash.sha1(input);
120
+ case "md5": return core.hash.md5(input);
121
+ case "b64e": return core.hash.base64Encode(input);
122
+ case "b64d": return core.hash.base64Decode(input);
123
+ }
124
+ })
125
+ );
126
+
127
+ await server.connect(new StdioServerTransport());
package/package.json CHANGED
@@ -1,6 +1,15 @@
1
1
  {
2
2
  "name": "subwrench-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "MCP server: subtitle convert/shift/merge, text stats, LLM token estimation, JSON tools, hashes. Zero external services, all local.",
5
+ "type": "module",
6
+ "bin": {
7
+ "subwrench-mcp": "./bin/server.js"
8
+ },
9
+ "dependencies": {
10
+ "subwrench-core": "0.1.0",
11
+ "@modelcontextprotocol/sdk": "^1.12.0",
12
+ "zod": "^3.24.0"
13
+ },
14
+ "license": "MIT"
15
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.