@hypothesi/tauri-mcp-server 0.2.1 → 0.3.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.
@@ -1,213 +0,0 @@
1
- import { z } from 'zod';
2
- import fs from 'fs/promises';
3
- import path from 'path';
4
- import { execa } from 'execa';
5
- export const GetDocsSchema = z.object({
6
- projectPath: z.string().describe('Path to the Tauri project'),
7
- });
8
- export async function getDocs(projectPath) {
9
- try {
10
- const version = await getTauriVersion(projectPath), isV2 = version.startsWith('2'), docs = await fetchDocs(version, isV2);
11
- return docs;
12
- }
13
- catch (error) {
14
- return `Error getting docs: ${error}`;
15
- }
16
- }
17
- async function getTauriVersion(projectPath) {
18
- try {
19
- // 1. Try 'cargo tree' to get the exact resolved version of the tauri crate
20
- try {
21
- const { stdout } = await execa('cargo', ['tree', '-p', 'tauri', '--depth', '0'], { cwd: path.join(projectPath, 'src-tauri') }), match = stdout.match(/tauri v([\d.]+)/);
22
- if (match && match[1]) {
23
- return match[1];
24
- }
25
- }
26
- catch (e) {
27
- // Ignore
28
- }
29
- // 2. Try 'npm list' for the CLI version
30
- try {
31
- const { stdout } = await execa('npm', ['list', '@tauri-apps/cli', '--depth=0', '--json'], { cwd: projectPath }), pkg = JSON.parse(stdout);
32
- if (pkg.dependencies?.['@tauri-apps/cli']?.version) {
33
- return pkg.dependencies['@tauri-apps/cli'].version;
34
- }
35
- }
36
- catch (e) {
37
- // Ignore
38
- }
39
- // 3. Fallback: Read Cargo.toml manually
40
- const cargoPath = path.join(projectPath, 'src-tauri', 'Cargo.toml'), cargoContent = await fs.readFile(cargoPath, 'utf-8'), match = cargoContent.match(/tauri\s*=\s*{?[^}]*version\s*=\s*"([^"]+)"/);
41
- if (match && match[1]) {
42
- return match[1];
43
- }
44
- // 4. Fallback: Read package.json
45
- const pkgPath = path.join(projectPath, 'package.json'), pkgContent = await fs.readFile(pkgPath, 'utf-8'), pkgJson = JSON.parse(pkgContent), cliVersion = pkgJson.devDependencies?.['@tauri-apps/cli'] || pkgJson.dependencies?.['@tauri-apps/cli'];
46
- if (cliVersion) {
47
- return cliVersion.replace('^', '').replace('~', '');
48
- }
49
- return 'unknown';
50
- }
51
- catch (e) {
52
- return 'unknown';
53
- }
54
- }
55
- // Exported for testing
56
- export async function fetchDocs(version, isV2) {
57
- const branch = isV2 ? 'v2' : 'v1', treeUrl = `https://api.github.com/repos/tauri-apps/tauri-docs/git/trees/${branch}?recursive=1`, rawBaseUrl = `https://raw.githubusercontent.com/tauri-apps/tauri-docs/${branch}`;
58
- try {
59
- // Fetching file tree from ${treeUrl}...
60
- const treeResponse = await fetch(treeUrl, {
61
- headers: {
62
- 'User-Agent': 'mcp-server-tauri', // GitHub API requires User-Agent
63
- },
64
- });
65
- if (!treeResponse.ok) {
66
- throw new Error(`Failed to fetch file tree: ${treeResponse.status} ${treeResponse.statusText}`);
67
- }
68
- const treeData = (await treeResponse.json()), relevantFiles = filterRelevantFiles(treeData.tree, isV2);
69
- // Found ${relevantFiles.length} relevant documentation files.
70
- let combinedDocs = `# Tauri ${isV2 ? 'v2' : 'v1'} Documentation (Dynamically Fetched)\n\n`;
71
- combinedDocs += `Version Detected: ${version}\n`;
72
- combinedDocs += `Source: ${rawBaseUrl}\n\n`;
73
- // Fetch content in batches to be polite and avoid timeouts
74
- const batchSize = 5;
75
- for (let i = 0; i < relevantFiles.length; i += batchSize) {
76
- const batch = relevantFiles.slice(i, i + batchSize), results = await Promise.all(batch.map((file) => { return fetchContent(rawBaseUrl, file); }));
77
- combinedDocs += results.join('\n\n');
78
- }
79
- return combinedDocs;
80
- }
81
- catch (e) {
82
- // Error fetching dynamic docs: ${e}
83
- return `Error fetching dynamic docs: ${e}\n\n` + getStaticFallback(version, isV2);
84
- }
85
- }
86
- function isExcludedPath(filePath) {
87
- // Check for common exclusions
88
- if (filePath.includes('/blog/') || filePath.includes('/_') || filePath.includes('/translations/')) {
89
- return true;
90
- }
91
- // Check file extensions
92
- if (!filePath.endsWith('.md') && !filePath.endsWith('.mdx')) {
93
- return true;
94
- }
95
- // Check for hidden or node_modules
96
- if (filePath.startsWith('.') || filePath.includes('/node_modules/')) {
97
- return true;
98
- }
99
- // Check for blog posts
100
- if (filePath.startsWith('blog/') || filePath.includes('/blog/')) {
101
- return true;
102
- }
103
- return false;
104
- }
105
- function isTranslationPath(filePath) {
106
- const langCodes = ['fr', 'es', 'it', 'ja', 'ko', 'zh-cn', 'zh-tw', 'pt-br', 'ru', 'de'];
107
- for (const lang of langCodes) {
108
- const hasLang = filePath.includes(`/${lang}/`) || filePath.includes(`/_${lang}/`) ||
109
- filePath.startsWith(`${lang}/`) || filePath.startsWith(`_${lang}/`);
110
- if (hasLang) {
111
- return true;
112
- }
113
- }
114
- return false;
115
- }
116
- function filterRelevantFiles(tree, isV2) {
117
- return tree.filter((item) => {
118
- if (item.type !== 'blob') {
119
- return false;
120
- }
121
- const file = item;
122
- if (isExcludedPath(file.path) || isTranslationPath(file.path)) {
123
- return false;
124
- }
125
- if (isV2) {
126
- // v2 docs are in src/content/docs
127
- if (!file.path.startsWith('src/content/docs/')) {
128
- return false;
129
- }
130
- // Exclude fragments and templates
131
- if (file.path.includes('/_fragments/') || file.path.includes('/.templates/')) {
132
- return false;
133
- }
134
- }
135
- else {
136
- // v1 docs are in docs/
137
- if (!file.path.startsWith('docs/')) {
138
- return false;
139
- }
140
- // Exclude templates
141
- if (file.path.includes('/.templates/')) {
142
- return false;
143
- }
144
- }
145
- return true;
146
- });
147
- }
148
- async function fetchContent(baseUrl, file) {
149
- try {
150
- const url = `${baseUrl}/${file.path}`, response = await fetch(url);
151
- if (!response.ok) {
152
- return `## ${file.path}\n\n(Failed to fetch: ${response.status})\n`;
153
- }
154
- const text = await response.text();
155
- // Remove frontmatter
156
- const cleanText = text.replace(/^---[\s\S]*?---\n/, '');
157
- return `## ${file.path}\n\n${cleanText}\n\n---\n`;
158
- }
159
- catch (e) {
160
- return `## ${file.path}\n\n(Error fetching content: ${e})\n`;
161
- }
162
- }
163
- function getStaticFallback(version, isV2) {
164
- if (isV2) {
165
- return `# Tauri v2 Static Fallback
166
- (Dynamic fetching failed. This is a minimal fallback.)
167
-
168
- ## Core Concepts
169
- - **Frontend**: Web technologies (HTML/CSS/JS).
170
- - **Backend**: Rust.
171
- - **IPC**: Use \`invoke\` to call Rust commands.
172
-
173
- ## Security
174
- - Enable capabilities in \`src-tauri/capabilities\`.
175
- - Configure permissions in \`tauri.conf.json\`.
176
- `;
177
- }
178
- return `# Tauri v1 LLM Cheat Sheet
179
- Version Detected: ${version}
180
- Documentation: https://tauri.app/v1/api/
181
-
182
- ## Core Concepts
183
- - **Frontend**: Web technologies.
184
- - **Backend**: Rust.
185
- - **IPC**: \`invoke\` and \`#[tauri::command]\`.
186
-
187
- ## Key APIs (Frontend)
188
- \`\`\`typescript
189
- import { invoke } from '@tauri-apps/api/tauri';
190
-
191
- // Call Rust command
192
- await invoke('my_command', { arg: 'value' });
193
- \`\`\`
194
-
195
- ## Key APIs (Rust)
196
- \`\`\`rust
197
- #[tauri::command]
198
- fn my_command(arg: String) -> String {
199
- format!("Hello {}", arg)
200
- }
201
-
202
- fn main() {
203
- tauri::Builder::default()
204
- .invoke_handler(tauri::generate_handler![my_command])
205
- .run(tauri::generate_context!())
206
- .expect("error while running tauri application");
207
- }
208
- \`\`\`
209
-
210
- ## Configuration
211
- - **tauri.conf.json**: Uses \`allowlist\` to enable features.
212
- `;
213
- }