@tanstack/cli 0.60.0 → 0.61.1

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/mcp/tools.ts DELETED
@@ -1,323 +0,0 @@
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
- }
package/src/mcp/types.ts DELETED
@@ -1,46 +0,0 @@
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>
package/src/mcp.ts DELETED
@@ -1,263 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
- import express from 'express'
5
- import { z } from 'zod'
6
-
7
- import {
8
- createApp,
9
- createDefaultEnvironment,
10
- finalizeAddOns,
11
- getFrameworkByName,
12
- getFrameworks,
13
- populateAddOnOptionsDefaults,
14
- } from '@tanstack/create'
15
-
16
- import { registerDocTools } from './mcp/tools.js'
17
-
18
- function createServer({
19
- appName,
20
- forcedAddOns = [],
21
- }: {
22
- appName?: string
23
- forcedAddOns?: Array<string>
24
- name?: string
25
- }) {
26
- const server = new McpServer({
27
- name: `${appName} Application Builder`,
28
- version: '1.0.0',
29
- })
30
-
31
- const frameworks = getFrameworks()
32
- const frameworkNames = frameworks.map((framework) => framework.name)
33
-
34
- server.tool(
35
- 'listTanStackAddOns',
36
- 'List the available add-ons for creating TanStack applications',
37
- {
38
- framework: z
39
- .string()
40
- .describe(
41
- `The framework to use. Available frameworks: ${frameworkNames.join(', ')}`,
42
- ),
43
- },
44
- ({ framework: frameworkName }) => {
45
- const framework = getFrameworkByName(frameworkName)!
46
- return {
47
- content: [
48
- {
49
- type: 'text',
50
- text: JSON.stringify(
51
- framework
52
- .getAddOns()
53
- .filter((addOn) => addOn.modes.includes('file-router'))
54
- .map((addOn) => ({
55
- id: addOn.id,
56
- name: addOn.name,
57
- description: addOn.description,
58
- type: addOn.type,
59
- category: addOn.category,
60
- link: addOn.link,
61
- warning: addOn.warning,
62
- exclusive: addOn.exclusive,
63
- options: addOn.options,
64
- dependsOn: addOn.dependsOn,
65
- })),
66
- ),
67
- },
68
- ],
69
- }
70
- },
71
- )
72
-
73
- server.tool(
74
- 'getAddOnDetails',
75
- 'Get detailed information about a specific add-on including implementation patterns, routes, dependencies, and documentation',
76
- {
77
- framework: z
78
- .string()
79
- .describe(
80
- `The framework to use. Available frameworks: ${frameworkNames.join(', ')}`,
81
- ),
82
- addOnId: z
83
- .string()
84
- .describe('The ID of the add-on to get details for'),
85
- },
86
- async ({ framework: frameworkName, addOnId }) => {
87
- const framework = getFrameworkByName(frameworkName)!
88
- const allAddOns = framework.getAddOns()
89
- const addOn =
90
- allAddOns.find((a) => a.id === addOnId) ??
91
- allAddOns.find(
92
- (a) => a.id.toLowerCase() === addOnId.toLowerCase(),
93
- )
94
-
95
- if (!addOn) {
96
- return {
97
- content: [
98
- {
99
- type: 'text',
100
- text: JSON.stringify({ error: `Add-on '${addOnId}' not found` }),
101
- },
102
- ],
103
- }
104
- }
105
-
106
- // Get file list for context
107
- const files = await addOn.getFiles()
108
-
109
- return {
110
- content: [
111
- {
112
- type: 'text',
113
- text: JSON.stringify({
114
- id: addOn.id,
115
- name: addOn.name,
116
- description: addOn.description,
117
- type: addOn.type,
118
- category: addOn.category,
119
- phase: addOn.phase,
120
- modes: addOn.modes,
121
- link: addOn.link,
122
- warning: addOn.warning,
123
- exclusive: addOn.exclusive,
124
- dependsOn: addOn.dependsOn,
125
- options: addOn.options,
126
- routes: addOn.routes,
127
- packageAdditions: addOn.packageAdditions,
128
- shadcnComponents: addOn.shadcnComponents,
129
- integrations: addOn.integrations,
130
- readme: addOn.readme,
131
- files,
132
- author: addOn.author,
133
- version: addOn.version,
134
- license: addOn.license,
135
- }),
136
- },
137
- ],
138
- }
139
- },
140
- )
141
-
142
- server.tool(
143
- 'createTanStackApplication',
144
- 'Create a new TanStack application',
145
- {
146
- framework: z
147
- .string()
148
- .describe(
149
- `The framework to use. Available frameworks: ${frameworkNames.join(', ')}`,
150
- ),
151
- projectName: z
152
- .string()
153
- .describe(
154
- 'The package.json module name of the application (will also be the directory name)',
155
- ),
156
- cwd: z.string().describe('The directory to create the application in'),
157
- addOns: z.array(z.string()).describe('Array of add-on IDs to install. Use listTanStackAddOns tool to see available add-ons and their configuration options. Example: ["prisma", "shadcn", "tanstack-query"]'),
158
- addOnOptions: z.record(z.record(z.any())).optional().describe('Configuration options for add-ons. Format: {"addOnId": {"optionName": "value"}}. Use listTanStackAddOns to see available options for each add-on.'),
159
- targetDir: z
160
- .string()
161
- .describe(
162
- 'The directory to create the application in. Use the absolute path of the directory you want the application to be created in',
163
- ),
164
- },
165
- async ({
166
- framework: frameworkName,
167
- projectName,
168
- addOns,
169
- addOnOptions,
170
- cwd,
171
- targetDir,
172
- }) => {
173
- const framework = getFrameworkByName(frameworkName)!
174
- try {
175
- process.chdir(cwd)
176
- try {
177
- const chosenAddOns = await finalizeAddOns(
178
- framework,
179
- 'file-router',
180
- Array.from(
181
- new Set([
182
- ...(addOns as unknown as Array<string>),
183
- ...forcedAddOns,
184
- ]),
185
- ),
186
- )
187
- await createApp(createDefaultEnvironment(), {
188
- projectName: projectName.replace(/^\//, './'),
189
- targetDir,
190
- framework,
191
- typescript: true,
192
- tailwind: true,
193
- packageManager: 'pnpm',
194
- mode: 'file-router',
195
- chosenAddOns,
196
- addOnOptions: addOnOptions || populateAddOnOptionsDefaults(chosenAddOns),
197
- git: true,
198
- })
199
- } catch (error) {
200
- console.error(error)
201
- return {
202
- content: [
203
- { type: 'text', text: `Error creating application: ${error}` },
204
- ],
205
- }
206
- }
207
- return {
208
- content: [{ type: 'text', text: 'Application created successfully' }],
209
- }
210
- } catch (error) {
211
- return {
212
- content: [
213
- { type: 'text', text: `Error creating application: ${error}` },
214
- ],
215
- }
216
- }
217
- },
218
- )
219
-
220
- // Register doc/ecosystem tools from TanStack API
221
- registerDocTools(server)
222
-
223
- return server
224
- }
225
-
226
- export async function runMCPServer(
227
- sse: boolean,
228
- {
229
- forcedAddOns,
230
- appName,
231
- name,
232
- }: {
233
- forcedAddOns?: Array<string>
234
- appName?: string
235
- name?: string
236
- },
237
- ) {
238
- let transport: SSEServerTransport | null = null
239
-
240
- const server = createServer({ appName, forcedAddOns, name })
241
- if (sse) {
242
- const app = express()
243
-
244
- app.get('/sse', (req, res) => {
245
- transport = new SSEServerTransport('/messages', res)
246
- server.connect(transport)
247
- })
248
-
249
- app.post('/messages', (req, res) => {
250
- if (transport) {
251
- transport.handlePostMessage(req, res)
252
- }
253
- })
254
-
255
- const port = process.env.PORT || 8080
256
- app.listen(port, () => {
257
- console.log(`Server is running on port http://localhost:${port}/sse`)
258
- })
259
- } else {
260
- const transport = new StdioServerTransport()
261
- await server.connect(transport)
262
- }
263
- }