@tanstack/cli 0.0.2 → 0.0.3

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.
@@ -156,9 +156,11 @@ export const IntegrationInfoSchema = z.object({
156
156
  requiresTailwind: z.boolean().optional(),
157
157
  demoRequiresTailwind: z.boolean().optional(),
158
158
 
159
- // Dependencies & conflicts
159
+ // Dependencies
160
160
  dependsOn: z.array(z.string()).optional(),
161
- conflicts: z.array(z.string()).optional(),
161
+ // Exclusive types - only one integration per exclusive type can be selected
162
+ // e.g., exclusive: ['deploy'] means only one deploy integration at a time
163
+ exclusive: z.array(z.string()).optional(),
162
164
 
163
165
  // Partner integration
164
166
  partnerId: z.string().optional(),
@@ -250,7 +252,8 @@ export const ManifestIntegrationSchema = z.object({
250
252
  category: CategorySchema.optional(),
251
253
  modes: z.array(RouterModeSchema),
252
254
  dependsOn: z.array(z.string()).optional(),
253
- conflicts: z.array(z.string()).optional(),
255
+ // Exclusive types - only one integration per exclusive type can be selected
256
+ exclusive: z.array(z.string()).optional(),
254
257
  partnerId: z.string().optional(),
255
258
  hasOptions: z.boolean().optional(),
256
259
  link: z.string().optional(),
@@ -269,7 +272,6 @@ export const ManifestCustomTemplateSchema = z.object({
269
272
  icon: z.string().optional(),
270
273
  features: z.array(z.string()).optional(),
271
274
  })
272
- export type ManifestCustomTemplate = z.infer<typeof ManifestCustomTemplateSchema>
273
275
 
274
276
  export const ManifestSchema = z.object({
275
277
  version: z.string(),
package/src/mcp/api.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { LibrariesResponseSchema, PartnersResponseSchema } from './types.js'
2
+ import type { LibrariesResponse, PartnersResponse } from './types.js'
3
+
4
+ const TANSTACK_API_BASE = 'https://tanstack.com/api/data'
5
+
6
+ export async function fetchLibraries(): Promise<LibrariesResponse> {
7
+ const response = await fetch(`${TANSTACK_API_BASE}/libraries`)
8
+ if (!response.ok) {
9
+ throw new Error(`Failed to fetch libraries: ${response.statusText}`)
10
+ }
11
+ const data = await response.json()
12
+ return LibrariesResponseSchema.parse(data)
13
+ }
14
+
15
+ export async function fetchPartners(): Promise<PartnersResponse> {
16
+ const response = await fetch(`${TANSTACK_API_BASE}/partners`)
17
+ if (!response.ok) {
18
+ throw new Error(`Failed to fetch partners: ${response.statusText}`)
19
+ }
20
+ const data = await response.json()
21
+ return PartnersResponseSchema.parse(data)
22
+ }
23
+
24
+ export async function fetchDocContent(
25
+ repo: string,
26
+ branch: string,
27
+ filePath: string,
28
+ ): Promise<string | null> {
29
+ const url = `https://raw.githubusercontent.com/${repo}/${branch}/${filePath}`
30
+ const response = await fetch(url, {
31
+ headers: { 'User-Agent': 'tanstack-cli' },
32
+ })
33
+
34
+ if (!response.ok) {
35
+ if (response.status === 404) {
36
+ return null
37
+ }
38
+ throw new Error(`Failed to fetch doc: ${response.statusText}`)
39
+ }
40
+
41
+ return response.text()
42
+ }
@@ -0,0 +1,323 @@
1
+ import { z } from 'zod'
2
+ import { fetchDocContent, fetchLibraries, fetchPartners } from './api.js'
3
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
4
+
5
+ // Algolia config (public read-only keys)
6
+ const ALGOLIA_APP_ID = 'FQ0DQ6MA3C'
7
+ const ALGOLIA_API_KEY = '10c34d6a5c89f6048cf644d601e65172'
8
+ const ALGOLIA_INDEX = 'tanstack-test'
9
+
10
+ const GROUP_KEYS = ['state', 'headlessUI', 'performance', 'tooling'] as const
11
+
12
+ function jsonResult(data: unknown) {
13
+ return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] }
14
+ }
15
+
16
+ function errorResult(error: string) {
17
+ return { content: [{ type: 'text' as const, text: `Error: ${error}` }], isError: true }
18
+ }
19
+
20
+ export function registerDocTools(server: McpServer) {
21
+ // Tool: tanstack_list_libraries
22
+ server.tool(
23
+ 'tanstack_list_libraries',
24
+ 'List TanStack libraries with metadata, frameworks, and docs URLs.',
25
+ {
26
+ group: z
27
+ .enum(GROUP_KEYS)
28
+ .optional()
29
+ .describe('Filter libraries by group. Options: state, headlessUI, performance, tooling'),
30
+ },
31
+ async ({ group }) => {
32
+ try {
33
+ const data = await fetchLibraries()
34
+ let libraries = data.libraries
35
+
36
+ if (group && data.groups[group]) {
37
+ const groupIds = data.groups[group]
38
+ libraries = libraries.filter((lib) => groupIds.includes(lib.id))
39
+ }
40
+
41
+ const groupName = group ? data.groupNames[group] || group : 'All Libraries'
42
+
43
+ return jsonResult({
44
+ group: groupName,
45
+ count: libraries.length,
46
+ libraries: libraries.map((lib) => ({
47
+ id: lib.id,
48
+ name: lib.name,
49
+ tagline: lib.tagline,
50
+ description: lib.description,
51
+ frameworks: lib.frameworks,
52
+ latestVersion: lib.latestVersion,
53
+ docsUrl: lib.docsUrl,
54
+ githubUrl: lib.githubUrl,
55
+ })),
56
+ })
57
+ } catch (error) {
58
+ return errorResult(String(error))
59
+ }
60
+ },
61
+ )
62
+
63
+ // Tool: tanstack_doc
64
+ server.tool(
65
+ 'tanstack_doc',
66
+ 'Fetch a TanStack documentation page by library and path.',
67
+ {
68
+ library: z.string().describe('Library ID (e.g., query, router, table, form)'),
69
+ path: z.string().describe('Documentation path (e.g., framework/react/overview)'),
70
+ version: z.string().optional().describe('Version (e.g., v5, v1). Defaults to latest'),
71
+ },
72
+ async ({ library: libraryId, path, version = 'latest' }) => {
73
+ try {
74
+ const data = await fetchLibraries()
75
+ const library = data.libraries.find((l) => l.id === libraryId)
76
+
77
+ if (!library) {
78
+ return errorResult(
79
+ `Library "${libraryId}" not found. Use tanstack_list_libraries to see available libraries.`,
80
+ )
81
+ }
82
+
83
+ if (version !== 'latest' && !library.availableVersions.includes(version)) {
84
+ return errorResult(
85
+ `Version "${version}" not found for ${library.name}. Available: ${library.availableVersions.join(', ')}`,
86
+ )
87
+ }
88
+
89
+ // Resolve branch
90
+ const branch =
91
+ version === 'latest' || version === library.latestVersion
92
+ ? library.latestBranch || 'main'
93
+ : version
94
+
95
+ const docsRoot = library.docsRoot || 'docs'
96
+ const filePath = `${docsRoot}/${path}.md`
97
+ const content = await fetchDocContent(library.repo, branch, filePath)
98
+
99
+ if (!content) {
100
+ return errorResult(
101
+ `Document not found: ${library.name} / ${path} (version: ${version})`,
102
+ )
103
+ }
104
+
105
+ // Extract frontmatter title if present
106
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
107
+ let title = path.split('/').pop() || 'Untitled'
108
+ let docContent = content
109
+
110
+ if (frontmatterMatch && frontmatterMatch[1]) {
111
+ const frontmatter = frontmatterMatch[1]
112
+ const titleMatch = frontmatter.match(/title:\s*['"]?([^'"\n]+)['"]?/)
113
+ if (titleMatch && titleMatch[1]) {
114
+ title = titleMatch[1]
115
+ }
116
+ docContent = content.slice(frontmatterMatch[0].length).trim()
117
+ }
118
+
119
+ return jsonResult({
120
+ title,
121
+ content: docContent,
122
+ url: `https://tanstack.com/${libraryId}/${version}/docs/${path}`,
123
+ library: library.name,
124
+ version: version === 'latest' ? library.latestVersion : version,
125
+ })
126
+ } catch (error) {
127
+ return errorResult(String(error))
128
+ }
129
+ },
130
+ )
131
+
132
+ // Tool: tanstack_search_docs
133
+ server.tool(
134
+ 'tanstack_search_docs',
135
+ 'Search TanStack documentation. Returns matching pages with snippets.',
136
+ {
137
+ query: z.string().describe('Search query'),
138
+ library: z.string().optional().describe('Filter to specific library (e.g., query, router)'),
139
+ framework: z
140
+ .string()
141
+ .optional()
142
+ .describe('Filter to specific framework (e.g., react, vue, solid)'),
143
+ limit: z
144
+ .number()
145
+ .min(1)
146
+ .max(50)
147
+ .optional()
148
+ .describe('Maximum number of results (default: 10, max: 50)'),
149
+ },
150
+ async ({ query, library, framework, limit = 10 }) => {
151
+ try {
152
+ const ALL_LIBRARIES = [
153
+ 'config', 'form', 'optimistic', 'pacer', 'query', 'ranger',
154
+ 'react-charts', 'router', 'start', 'store', 'table', 'virtual', 'db', 'devtools',
155
+ ]
156
+ const ALL_FRAMEWORKS = ['react', 'vue', 'solid', 'svelte', 'angular']
157
+
158
+ // Build filters
159
+ const filterParts: Array<string> = ['version:latest']
160
+
161
+ if (library) {
162
+ const otherLibraries = ALL_LIBRARIES.filter((l) => l !== library)
163
+ const exclusions = otherLibraries.map((l) => `NOT library:${l}`).join(' AND ')
164
+ if (exclusions) filterParts.push(`(${exclusions})`)
165
+ }
166
+
167
+ if (framework) {
168
+ const otherFrameworks = ALL_FRAMEWORKS.filter((f) => f !== framework)
169
+ const exclusions = otherFrameworks.map((f) => `NOT framework:${f}`).join(' AND ')
170
+ if (exclusions) filterParts.push(`(${exclusions})`)
171
+ }
172
+
173
+ // Call Algolia REST API directly
174
+ const searchParams = {
175
+ requests: [
176
+ {
177
+ indexName: ALGOLIA_INDEX,
178
+ query,
179
+ hitsPerPage: Math.min(limit, 50),
180
+ filters: filterParts.join(' AND '),
181
+ attributesToRetrieve: ['hierarchy', 'url', 'content', 'library'],
182
+ attributesToSnippet: ['content:80'],
183
+ },
184
+ ],
185
+ }
186
+
187
+ const response = await fetch(
188
+ `https://${ALGOLIA_APP_ID}-dsn.algolia.net/1/indexes/*/queries`,
189
+ {
190
+ method: 'POST',
191
+ headers: {
192
+ 'Content-Type': 'application/json',
193
+ 'X-Algolia-Application-Id': ALGOLIA_APP_ID,
194
+ 'X-Algolia-API-Key': ALGOLIA_API_KEY,
195
+ },
196
+ body: JSON.stringify(searchParams),
197
+ },
198
+ )
199
+
200
+ if (!response.ok) {
201
+ return errorResult(`Algolia search failed: ${response.statusText}`)
202
+ }
203
+
204
+ const searchResponse = await response.json() as {
205
+ results: Array<{
206
+ hits: Array<{
207
+ objectID: string
208
+ url: string
209
+ library?: string
210
+ hierarchy: Record<string, string | undefined>
211
+ content?: string
212
+ _snippetResult?: { content?: { value?: string } }
213
+ }>
214
+ nbHits?: number
215
+ }>
216
+ }
217
+
218
+ const searchResult = searchResponse.results[0]
219
+ if (!searchResult) {
220
+ return jsonResult({ query, totalHits: 0, results: [] })
221
+ }
222
+
223
+ const results = searchResult.hits.map((hit) => {
224
+ const breadcrumb = Object.values(hit.hierarchy).filter((v): v is string => Boolean(v))
225
+ return {
226
+ title: hit.hierarchy.lvl1 || hit.hierarchy.lvl0 || 'Untitled',
227
+ url: hit.url,
228
+ snippet: hit._snippetResult?.content?.value || hit.content || '',
229
+ library: hit.library || 'unknown',
230
+ breadcrumb,
231
+ }
232
+ })
233
+
234
+ return jsonResult({
235
+ query,
236
+ totalHits: searchResult.nbHits || results.length,
237
+ results,
238
+ })
239
+ } catch (error) {
240
+ return errorResult(String(error))
241
+ }
242
+ },
243
+ )
244
+
245
+ // Tool: tanstack_ecosystem
246
+ server.tool(
247
+ 'tanstack_ecosystem',
248
+ 'Ecosystem partner recommendations. Filter by category (database, auth, deployment, monitoring, cms, api, data-grid) or library.',
249
+ {
250
+ category: z
251
+ .string()
252
+ .optional()
253
+ .describe(
254
+ 'Filter by category: database, auth, deployment, monitoring, cms, api, data-grid, code-review, learning',
255
+ ),
256
+ library: z
257
+ .string()
258
+ .optional()
259
+ .describe('Filter by TanStack library (e.g., start, router, query, table)'),
260
+ },
261
+ async ({ category, library }) => {
262
+ try {
263
+ const data = await fetchPartners()
264
+
265
+ // Category aliases
266
+ const categoryAliases: Record<string, string> = {
267
+ db: 'database',
268
+ postgres: 'database',
269
+ sql: 'database',
270
+ login: 'auth',
271
+ authentication: 'auth',
272
+ hosting: 'deployment',
273
+ deploy: 'deployment',
274
+ serverless: 'deployment',
275
+ errors: 'monitoring',
276
+ logging: 'monitoring',
277
+ content: 'cms',
278
+ 'api-keys': 'api',
279
+ grid: 'data-grid',
280
+ review: 'code-review',
281
+ courses: 'learning',
282
+ }
283
+
284
+ let resolvedCategory: string | undefined
285
+ if (category) {
286
+ const normalized = category.toLowerCase().trim()
287
+ resolvedCategory = categoryAliases[normalized] || normalized
288
+ if (!data.categories.includes(resolvedCategory)) {
289
+ resolvedCategory = undefined
290
+ }
291
+ }
292
+
293
+ const lib = library?.toLowerCase().trim()
294
+
295
+ const partners = data.partners
296
+ .filter((p) => !resolvedCategory || p.category === resolvedCategory)
297
+ .filter((p) => !lib || p.libraries.some((l) => l === lib))
298
+ .map((p) => ({
299
+ id: p.id,
300
+ name: p.name,
301
+ tagline: p.tagline,
302
+ description: p.description,
303
+ category: p.category,
304
+ categoryLabel: p.categoryLabel,
305
+ url: p.url,
306
+ libraries: p.libraries,
307
+ }))
308
+
309
+ return jsonResult({
310
+ query: {
311
+ category,
312
+ categoryResolved: resolvedCategory,
313
+ library,
314
+ },
315
+ count: partners.length,
316
+ partners,
317
+ })
318
+ } catch (error) {
319
+ return errorResult(String(error))
320
+ }
321
+ },
322
+ )
323
+ }
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod'
2
+
3
+ // API response types from tanstack.com
4
+ export const LibrarySchema = z.object({
5
+ id: z.string(),
6
+ name: z.string(),
7
+ tagline: z.string(),
8
+ description: z.string().optional(),
9
+ frameworks: z.array(z.string()),
10
+ latestVersion: z.string(),
11
+ latestBranch: z.string().optional(),
12
+ availableVersions: z.array(z.string()),
13
+ repo: z.string(),
14
+ docsRoot: z.string().optional(),
15
+ defaultDocs: z.string().optional(),
16
+ docsUrl: z.string().optional(),
17
+ githubUrl: z.string().optional(),
18
+ })
19
+
20
+ export const LibrariesResponseSchema = z.object({
21
+ libraries: z.array(LibrarySchema),
22
+ groups: z.record(z.array(z.string())),
23
+ groupNames: z.record(z.string()),
24
+ })
25
+
26
+ export const PartnerSchema = z.object({
27
+ id: z.string(),
28
+ name: z.string(),
29
+ tagline: z.string().optional(),
30
+ description: z.string(),
31
+ category: z.string(),
32
+ categoryLabel: z.string(),
33
+ libraries: z.array(z.string()),
34
+ url: z.string(),
35
+ })
36
+
37
+ export const PartnersResponseSchema = z.object({
38
+ partners: z.array(PartnerSchema),
39
+ categories: z.array(z.string()),
40
+ categoryLabels: z.record(z.string()),
41
+ })
42
+
43
+ export type Library = z.infer<typeof LibrarySchema>
44
+ export type LibrariesResponse = z.infer<typeof LibrariesResponseSchema>
45
+ export type Partner = z.infer<typeof PartnerSchema>
46
+ export type PartnersResponse = z.infer<typeof PartnersResponseSchema>
@@ -841,6 +841,9 @@ ${packageManager}${packageManager === 'npm' ? ' run' : ''} dev
841
841
  - [TanStack Router Documentation](https://tanstack.com/router)
842
842
  `
843
843
 
844
+ // .nvmrc - Node version (TanStack Start requires 22.12.0+)
845
+ files['.nvmrc'] = '23'
846
+
844
847
  return files
845
848
  }
846
849