@tsrx/mcp 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/server.js ADDED
@@ -0,0 +1,629 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { z } from 'zod';
5
+ import {
6
+ find_documentation_section,
7
+ find_similar_documentation_sections,
8
+ list_documentation_sections,
9
+ } from './docs.js';
10
+ import { analyze_tsrx } from './analyze.js';
11
+ import { compile_tsrx } from './compile.js';
12
+ import { format_tsrx } from './format.js';
13
+ import { inspect_project } from './inspect.js';
14
+ import { validate_tsrx_file } from './validate.js';
15
+ import { detect_target } from './target.js';
16
+ import {
17
+ TARGET_SCHEMA,
18
+ analysis_result_schema,
19
+ compile_result_schema,
20
+ format_result_schema,
21
+ inspect_project_result_schema,
22
+ target_detection_schema,
23
+ validate_file_result_schema,
24
+ } from './schemas.js';
25
+
26
+ const package_json_path = fileURLToPath(new URL('../package.json', import.meta.url));
27
+ const package_json = JSON.parse(readFileSync(package_json_path, 'utf8'));
28
+
29
+ const SERVER_INFO = {
30
+ name: 'TSRX MCP Server',
31
+ version: typeof package_json.version === 'string' ? package_json.version : '0.0.0',
32
+ };
33
+
34
+ const TARGET_RESOURCE_CONTENT = {
35
+ react: `# TSRX React Target
36
+
37
+ The core TSRX MCP server owns target-neutral language syntax and compiler validation. React-specific guidance should live in a React target layer.
38
+
39
+ The React target layer should own:
40
+ - React package and bundler setup
41
+ - React runtime imports and helper APIs
42
+ - React JSX compatibility expectations
43
+ - React-specific compiler diagnostics and examples
44
+ - interop with React libraries and hooks
45
+
46
+ Before giving React-specific advice, use \`detect-target\` or an explicit target signal and validate generated .tsrx code with \`compile-tsrx\`.`,
47
+ preact: `# TSRX Preact Target
48
+
49
+ The core TSRX MCP server owns target-neutral language syntax and compiler validation. Preact-specific guidance should live in a Preact target layer.
50
+
51
+ The Preact target layer should own:
52
+ - Preact package and bundler setup
53
+ - Preact runtime imports and helper APIs
54
+ - Preact JSX compatibility expectations
55
+ - Preact-specific compiler diagnostics and examples
56
+ - interop with Preact libraries and hooks
57
+
58
+ Before giving Preact-specific advice, use \`detect-target\` or an explicit target signal and validate generated .tsrx code with \`compile-tsrx\`.`,
59
+ solid: `# TSRX Solid Target
60
+
61
+ The core TSRX MCP server owns target-neutral language syntax and compiler validation. Solid-specific guidance should live in a Solid target layer.
62
+
63
+ The Solid target layer should own:
64
+ - Solid package and bundler setup
65
+ - Solid runtime imports and helper APIs
66
+ - Solid JSX compatibility expectations
67
+ - Solid-specific compiler diagnostics and examples
68
+ - interop with Solid primitives and libraries
69
+
70
+ Before giving Solid-specific advice, use \`detect-target\` or an explicit target signal and validate generated .tsrx code with \`compile-tsrx\`.`,
71
+ vue: `# TSRX Vue Target
72
+
73
+ The core TSRX MCP server owns target-neutral language syntax and compiler validation. Vue-specific guidance should live in a Vue target layer.
74
+
75
+ The Vue target layer should own:
76
+ - Vue package and bundler setup
77
+ - Vue runtime imports and helper APIs
78
+ - Vue JSX/Vapor compatibility expectations
79
+ - Vue-specific compiler diagnostics and examples
80
+ - interop with Vue libraries and composition APIs
81
+
82
+ Before giving Vue-specific advice, use \`detect-target\` or an explicit target signal and validate generated .tsrx code with \`compile-tsrx\`.`,
83
+ ripple: `# TSRX Ripple Target
84
+
85
+ The core TSRX MCP server owns target-neutral language syntax and compiler validation. Ripple-specific guidance should live in a Ripple target layer.
86
+
87
+ The Ripple target layer should own:
88
+ - Ripple package and bundler setup
89
+ - Ripple runtime imports and helper APIs
90
+ - Ripple reactivity, server, and hydration semantics
91
+ - Ripple-specific compiler diagnostics and examples
92
+ - interop with Ripple runtime components and helpers
93
+
94
+ Before giving Ripple-specific advice, use \`detect-target\` or an explicit target signal and validate generated .tsrx code with \`compile-tsrx\`.`,
95
+ };
96
+
97
+ /**
98
+ * @param {unknown} value
99
+ * @returns {string[]}
100
+ */
101
+ function normalize_section_input(value) {
102
+ if (Array.isArray(value)) return value.filter((item) => typeof item === 'string');
103
+ if (typeof value !== 'string') return [];
104
+ const trimmed = value.trim();
105
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
106
+ try {
107
+ const parsed = JSON.parse(trimmed);
108
+ if (Array.isArray(parsed)) return parsed.filter((item) => typeof item === 'string');
109
+ } catch {
110
+ // Fall back to comma-separated handling.
111
+ }
112
+ }
113
+ return trimmed
114
+ .split(',')
115
+ .map((section) => section.trim())
116
+ .filter(Boolean);
117
+ }
118
+
119
+ export function list_sections_handler() {
120
+ return {
121
+ sections: list_documentation_sections().map(({ title, slug, use_cases }) => ({
122
+ title,
123
+ slug,
124
+ use_cases,
125
+ })),
126
+ };
127
+ }
128
+
129
+ /**
130
+ * @param {{ section: string | string[] }} input
131
+ */
132
+ export function get_documentation_handler(input) {
133
+ const requested_sections = normalize_section_input(input.section);
134
+ const found = [];
135
+ const missing = [];
136
+
137
+ for (const requested of requested_sections) {
138
+ const section = find_documentation_section(requested);
139
+ if (section) {
140
+ found.push(section);
141
+ } else {
142
+ missing.push({
143
+ section: requested,
144
+ similar: find_similar_documentation_sections(requested).map(({ title, slug }) => ({
145
+ title,
146
+ slug,
147
+ })),
148
+ });
149
+ }
150
+ }
151
+
152
+ return {
153
+ sections: found.map(({ title, slug, content }) => ({ title, slug, content })),
154
+ missing,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * @param {{ cwd?: string }} input
160
+ */
161
+ export function detect_target_handler(input = {}) {
162
+ return detect_target(input.cwd);
163
+ }
164
+
165
+ /**
166
+ * @param {{
167
+ * code: string,
168
+ * filename?: string,
169
+ * target?: string,
170
+ * cwd?: string,
171
+ * collect?: boolean,
172
+ * loose?: boolean,
173
+ * includeCode?: boolean,
174
+ * mode?: 'client' | 'server'
175
+ * }} input
176
+ */
177
+ export function compile_tsrx_handler(input) {
178
+ return compile_tsrx(input);
179
+ }
180
+
181
+ /**
182
+ * @param {{
183
+ * code: string,
184
+ * filename?: string,
185
+ * target?: string,
186
+ * cwd?: string,
187
+ * collect?: boolean,
188
+ * loose?: boolean,
189
+ * mode?: 'client' | 'server'
190
+ * }} input
191
+ */
192
+ export function analyze_tsrx_handler(input) {
193
+ return analyze_tsrx(input);
194
+ }
195
+
196
+ /**
197
+ * @param {{
198
+ * code: string,
199
+ * filename?: string,
200
+ * cwd?: string,
201
+ * printWidth?: number,
202
+ * tabWidth?: number,
203
+ * useTabs?: boolean,
204
+ * singleQuote?: boolean,
205
+ * check?: boolean
206
+ * }} input
207
+ */
208
+ export function format_tsrx_handler(input) {
209
+ return format_tsrx(input);
210
+ }
211
+
212
+ /**
213
+ * @param {{ cwd?: string }} input
214
+ */
215
+ export function inspect_project_handler(input = {}) {
216
+ return inspect_project(input);
217
+ }
218
+
219
+ /**
220
+ * @param {{
221
+ * filePath: string,
222
+ * cwd?: string,
223
+ * target?: string,
224
+ * collect?: boolean,
225
+ * loose?: boolean,
226
+ * mode?: 'client' | 'server',
227
+ * printWidth?: number,
228
+ * tabWidth?: number,
229
+ * useTabs?: boolean,
230
+ * singleQuote?: boolean
231
+ * }} input
232
+ */
233
+ export function validate_tsrx_file_handler(input) {
234
+ return validate_tsrx_file(input);
235
+ }
236
+
237
+ /**
238
+ * @param {unknown} value
239
+ */
240
+ function json_text(value) {
241
+ return JSON.stringify(value, null, 2);
242
+ }
243
+
244
+ /**
245
+ * @param {string} uri
246
+ * @param {string} text
247
+ */
248
+ function text_resource(uri, text) {
249
+ return {
250
+ contents: [
251
+ {
252
+ uri,
253
+ mimeType: 'text/markdown',
254
+ text,
255
+ },
256
+ ],
257
+ };
258
+ }
259
+
260
+ /**
261
+ * @param {{ remote: boolean }} options
262
+ */
263
+ function create_tsrx_task_prompt(options) {
264
+ const project_context_step = options.remote
265
+ ? '2. This hosted MCP endpoint cannot inspect a local project filesystem. Use an explicit `target` argument when compiling or analyzing code, or ask the user which target runtime they use.'
266
+ : '2. If project context exists, call `inspect-project` for package/tooling context or `detect-target` when only the runtime target is needed before assuming React, Preact, Solid, Vue, or Ripple semantics.';
267
+ const file_validation_step = options.remote
268
+ ? '6. For existing files, ask the user to paste source or use a local stdio MCP client; hosted MCP cannot read local file paths.'
269
+ : '6. When working with an existing file, call `validate-tsrx-file` for one-shot format, compile, and advice feedback.';
270
+ const compile_step = options.remote
271
+ ? '7. Before presenting generated .tsrx code as final, call `format-tsrx`, then `compile-tsrx` with an explicit target.'
272
+ : '7. Before presenting generated .tsrx code as final, call `format-tsrx`, then `compile-tsrx` with the inferred or explicit target.';
273
+
274
+ return `Use this workflow when helping with TSRX:
275
+
276
+ 1. Identify whether the task is about target-neutral TSRX syntax, target runtime behavior, or both.
277
+ ${project_context_step}
278
+ 3. For syntax uncertainty, use \`list-sections\`, \`get-documentation\`, or read \`tsrx://docs/{slug}.md\`.
279
+ 4. Keep core TSRX advice target-neutral: component declarations, statement templates, control flow, TSX expression values, lazy destructuring, style identifiers, and server blocks.
280
+ 5. Use \`tsrx://targets/{target}.md\` as the handoff point for target-specific responsibilities.
281
+ ${file_validation_step}
282
+ ${compile_step}
283
+ 8. If \`compile-tsrx\` returns diagnostics, call \`analyze-tsrx\` for targeted authoring advice, fix the code, format it, and compile again.
284
+ 9. Do not invent runtime APIs, imports, or bundler configuration from target-neutral TSRX docs. Use target-specific docs, resources, or skills for those details.`;
285
+ }
286
+
287
+ /**
288
+ * Create the shared TSRX MCP server. Transports are intentionally owned by wrapper
289
+ * packages so this package can be reused by stdio and hosted HTTP entry points.
290
+ *
291
+ * @param {{ remote?: boolean }} [options]
292
+ */
293
+ export function createTSRXMcpServer(options = {}) {
294
+ const remote = options.remote === true;
295
+ const server = new McpServer(SERVER_INFO, {
296
+ instructions: remote
297
+ ? 'Use this hosted server for target-neutral TSRX language guidance. It cannot inspect local project files, so pass an explicit runtime target before compiling .tsrx code. Fetch relevant docs sections when syntax details are uncertain, and use target-specific docs or resources for runtime APIs, imports, bundler setup, and framework semantics.'
298
+ : 'Use this server for target-neutral TSRX language guidance. Detect the runtime target before generating .tsrx code, fetch relevant docs sections when syntax details are uncertain, and use target-specific skills or resources for runtime APIs, imports, bundler setup, and framework semantics.',
299
+ });
300
+
301
+ server.registerTool(
302
+ 'list-sections',
303
+ {
304
+ title: 'List TSRX Documentation Sections',
305
+ description:
306
+ 'Lists available TSRX documentation sections with use_cases. Use this to discover relevant docs before answering TSRX syntax or target-runtime questions.',
307
+ outputSchema: {
308
+ sections: z.array(
309
+ z.object({
310
+ title: z.string(),
311
+ slug: z.string(),
312
+ use_cases: z.string(),
313
+ }),
314
+ ),
315
+ },
316
+ annotations: {
317
+ readOnlyHint: true,
318
+ destructiveHint: false,
319
+ openWorldHint: false,
320
+ },
321
+ },
322
+ async () => {
323
+ const output = list_sections_handler();
324
+ return {
325
+ content: [{ type: 'text', text: json_text(output) }],
326
+ structuredContent: output,
327
+ };
328
+ },
329
+ );
330
+
331
+ server.registerTool(
332
+ 'get-documentation',
333
+ {
334
+ title: 'Get TSRX Documentation',
335
+ description:
336
+ 'Retrieves TSRX documentation for one or more section slugs or titles. Pass a string, comma-separated string, JSON array string, or string array.',
337
+ inputSchema: {
338
+ section: z.union([z.string(), z.array(z.string())]),
339
+ },
340
+ outputSchema: {
341
+ sections: z.array(
342
+ z.object({
343
+ title: z.string(),
344
+ slug: z.string(),
345
+ content: z.string(),
346
+ }),
347
+ ),
348
+ missing: z.array(
349
+ z.object({
350
+ section: z.string(),
351
+ similar: z.array(
352
+ z.object({
353
+ title: z.string(),
354
+ slug: z.string(),
355
+ }),
356
+ ),
357
+ }),
358
+ ),
359
+ },
360
+ annotations: {
361
+ readOnlyHint: true,
362
+ destructiveHint: false,
363
+ openWorldHint: false,
364
+ },
365
+ },
366
+ async ({ section }) => {
367
+ const output = get_documentation_handler({ section });
368
+ return {
369
+ content: [{ type: 'text', text: json_text(output) }],
370
+ structuredContent: output,
371
+ };
372
+ },
373
+ );
374
+
375
+ for (const section of list_documentation_sections()) {
376
+ const uri = `tsrx://docs/${section.slug}.md`;
377
+ server.registerResource(
378
+ `tsrx-docs-${section.slug}`,
379
+ uri,
380
+ {
381
+ title: section.title,
382
+ description: section.use_cases,
383
+ mimeType: 'text/markdown',
384
+ },
385
+ async () => text_resource(uri, section.content),
386
+ );
387
+ }
388
+
389
+ for (const [target, content] of Object.entries(TARGET_RESOURCE_CONTENT)) {
390
+ const uri = `tsrx://targets/${target}.md`;
391
+ server.registerResource(
392
+ `tsrx-target-${target}`,
393
+ uri,
394
+ {
395
+ title: `TSRX ${target[0].toUpperCase()}${target.slice(1)} Target`,
396
+ description: `${target} target handoff guidance for TSRX agents`,
397
+ mimeType: 'text/markdown',
398
+ },
399
+ async () => text_resource(uri, content),
400
+ );
401
+ }
402
+
403
+ server.registerPrompt(
404
+ 'tsrx-task',
405
+ {
406
+ title: 'TSRX Task Workflow',
407
+ description:
408
+ 'Guide an agent through target-aware TSRX work: detect target, fetch docs, compile, and defer runtime-specific details to target layers.',
409
+ argsSchema: {
410
+ task: z.string().optional(),
411
+ target: z.enum(['ripple', 'react', 'preact', 'solid', 'vue']).optional(),
412
+ },
413
+ },
414
+ async ({ task, target }) => {
415
+ const context = [task ? `Task: ${task}` : null, target ? `Known target: ${target}` : null]
416
+ .filter(Boolean)
417
+ .join('\n');
418
+ const text = context
419
+ ? `${context}\n\n${create_tsrx_task_prompt({ remote })}`
420
+ : create_tsrx_task_prompt({ remote });
421
+
422
+ return {
423
+ description: 'Target-aware TSRX agent workflow',
424
+ messages: [
425
+ {
426
+ role: 'user',
427
+ content: {
428
+ type: 'text',
429
+ text,
430
+ },
431
+ },
432
+ ],
433
+ };
434
+ },
435
+ );
436
+
437
+ if (!remote) {
438
+ server.registerTool(
439
+ 'detect-target',
440
+ {
441
+ title: 'Detect TSRX Runtime Target',
442
+ description:
443
+ 'Inspects package.json and common bundler config files to infer whether a project uses TSRX with Ripple, React, Preact, Solid, or Vue.',
444
+ inputSchema: {
445
+ cwd: z.string().optional(),
446
+ },
447
+ outputSchema: {
448
+ ...target_detection_schema,
449
+ },
450
+ annotations: {
451
+ readOnlyHint: true,
452
+ destructiveHint: false,
453
+ openWorldHint: false,
454
+ },
455
+ },
456
+ async ({ cwd }) => {
457
+ const output = detect_target_handler({ cwd });
458
+ return {
459
+ content: [{ type: 'text', text: json_text(output) }],
460
+ structuredContent: output,
461
+ };
462
+ },
463
+ );
464
+ }
465
+
466
+ server.registerTool(
467
+ 'compile-tsrx',
468
+ {
469
+ title: 'Compile TSRX',
470
+ description:
471
+ 'Compiles TSRX code with the inferred or explicit runtime target compiler. Use this to validate generated .tsrx code and collect compiler diagnostics.',
472
+ inputSchema: {
473
+ code: z.string(),
474
+ filename: z.string().optional(),
475
+ target: TARGET_SCHEMA.optional(),
476
+ cwd: z.string().optional(),
477
+ collect: z.boolean().optional(),
478
+ loose: z.boolean().optional(),
479
+ includeCode: z.boolean().optional(),
480
+ mode: z.enum(['client', 'server']).optional(),
481
+ },
482
+ outputSchema: compile_result_schema,
483
+ annotations: {
484
+ readOnlyHint: true,
485
+ destructiveHint: false,
486
+ openWorldHint: false,
487
+ },
488
+ },
489
+ async (input) => {
490
+ const output = await compile_tsrx_handler(remote ? { ...input, cwd: undefined } : input);
491
+ if (remote) output.cwd = 'remote';
492
+ return {
493
+ content: [{ type: 'text', text: json_text(output) }],
494
+ structuredContent: output,
495
+ };
496
+ },
497
+ );
498
+
499
+ server.registerTool(
500
+ 'format-tsrx',
501
+ {
502
+ title: 'Format TSRX',
503
+ description:
504
+ 'Formats TSRX code using the official @tsrx/prettier-plugin. Use this before returning generated .tsrx code or to check whether source is already formatted.',
505
+ inputSchema: {
506
+ code: z.string(),
507
+ filename: z.string().optional(),
508
+ cwd: z.string().optional(),
509
+ printWidth: z.number().int().positive().optional(),
510
+ tabWidth: z.number().int().positive().optional(),
511
+ useTabs: z.boolean().optional(),
512
+ singleQuote: z.boolean().optional(),
513
+ check: z.boolean().optional(),
514
+ },
515
+ outputSchema: format_result_schema,
516
+ annotations: {
517
+ readOnlyHint: true,
518
+ destructiveHint: false,
519
+ openWorldHint: false,
520
+ },
521
+ },
522
+ async (input) => {
523
+ const output = await format_tsrx_handler(remote ? { ...input, cwd: undefined } : input);
524
+ if (remote) {
525
+ output.cwd = 'remote';
526
+ output.configPath = null;
527
+ }
528
+ return {
529
+ content: [{ type: 'text', text: json_text(output) }],
530
+ structuredContent: output,
531
+ };
532
+ },
533
+ );
534
+
535
+ server.registerTool(
536
+ 'analyze-tsrx',
537
+ {
538
+ title: 'Analyze TSRX Diagnostics',
539
+ description:
540
+ 'Compiles TSRX code and turns compiler diagnostics into target-neutral authoring advice with linked TSRX docs resources. Use this before manually guessing fixes.',
541
+ inputSchema: {
542
+ code: z.string(),
543
+ filename: z.string().optional(),
544
+ target: TARGET_SCHEMA.optional(),
545
+ cwd: z.string().optional(),
546
+ collect: z.boolean().optional(),
547
+ loose: z.boolean().optional(),
548
+ mode: z.enum(['client', 'server']).optional(),
549
+ },
550
+ outputSchema: analysis_result_schema,
551
+ annotations: {
552
+ readOnlyHint: true,
553
+ destructiveHint: false,
554
+ openWorldHint: false,
555
+ },
556
+ },
557
+ async (input) => {
558
+ const output = await analyze_tsrx_handler(remote ? { ...input, cwd: undefined } : input);
559
+ if (remote) output.cwd = 'remote';
560
+ return {
561
+ content: [{ type: 'text', text: json_text(output) }],
562
+ structuredContent: output,
563
+ };
564
+ },
565
+ );
566
+
567
+ if (!remote) {
568
+ server.registerTool(
569
+ 'inspect-project',
570
+ {
571
+ title: 'Inspect TSRX Project',
572
+ description:
573
+ 'Inspects package.json, target signals, TSRX packages, tooling packages, scripts, and common config files for a project. Use this before choosing target-specific advice or repo commands.',
574
+ inputSchema: {
575
+ cwd: z.string().optional(),
576
+ },
577
+ outputSchema: inspect_project_result_schema,
578
+ annotations: {
579
+ readOnlyHint: true,
580
+ destructiveHint: false,
581
+ openWorldHint: false,
582
+ },
583
+ },
584
+ async ({ cwd }) => {
585
+ const output = inspect_project_handler({ cwd });
586
+ return {
587
+ content: [{ type: 'text', text: json_text(output) }],
588
+ structuredContent: output,
589
+ };
590
+ },
591
+ );
592
+
593
+ server.registerTool(
594
+ 'validate-tsrx-file',
595
+ {
596
+ title: 'Validate TSRX File',
597
+ description:
598
+ 'Reads a .tsrx file and runs the full MCP validation loop: formatting check, target-aware compilation, and diagnostic advice. This tool is read-only and does not rewrite files.',
599
+ inputSchema: {
600
+ filePath: z.string(),
601
+ cwd: z.string().optional(),
602
+ target: TARGET_SCHEMA.optional(),
603
+ collect: z.boolean().optional(),
604
+ loose: z.boolean().optional(),
605
+ mode: z.enum(['client', 'server']).optional(),
606
+ printWidth: z.number().int().positive().optional(),
607
+ tabWidth: z.number().int().positive().optional(),
608
+ useTabs: z.boolean().optional(),
609
+ singleQuote: z.boolean().optional(),
610
+ },
611
+ outputSchema: validate_file_result_schema,
612
+ annotations: {
613
+ readOnlyHint: true,
614
+ destructiveHint: false,
615
+ openWorldHint: false,
616
+ },
617
+ },
618
+ async (input) => {
619
+ const output = await validate_tsrx_file_handler(input);
620
+ return {
621
+ content: [{ type: 'text', text: json_text(output) }],
622
+ structuredContent: output,
623
+ };
624
+ },
625
+ );
626
+ }
627
+
628
+ return server;
629
+ }
package/src/stdio.js ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createTSRXMcpServer } from './server.js';
4
+
5
+ const server = createTSRXMcpServer();
6
+ const transport = new StdioServerTransport();
7
+
8
+ await server.connect(transport);