-
Notifications
You must be signed in to change notification settings - Fork 4
feat: add install_devcycle_sdk MCP tool and build-time prompt fetcher #491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
dc00a80
feat: add install_devcycle_sdk MCP tool and build-time prompt fetcher
jonathannorris 65ec032
chore: build changes
jonathannorris d97918f
fix: harden build-time prompt fetcher against path traversal and unsa…
jonathannorris 0d493d4
chore: switch install prompt source to DevCycleHQ org and update runt…
jonathannorris 85dab56
fix: address review comments (remove unused apiClient param, global s…
jonathannorris ee74b1c
chore: format
jonathannorris a1bc074
chore: keep generated installGuides.generated.ts in repo but exclude …
jonathannorris 720a508
feat: update tool description
jonathannorris 7467855
chore: simplify script exit flow by removing empty then() on main()
jonathannorris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Mark auto-generated files so GitHub linguist ignores them in code stats | ||
| src/mcp/tools/installGuides.generated.ts linguist-generated |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| { | ||
| "version": "6.0.0", | ||
| "version": "6.0.1", | ||
| "commands": { | ||
| "authCommand": { | ||
| "id": "authCommand", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| #!/usr/bin/env node | ||
| /* | ||
| * Auto-generates src/mcp/tools/installGuides.generated.ts | ||
| * by listing all Markdown files under install-prompts/ (recursively) | ||
| * from the AI-Prompts-And-Rules repo on the main branch. | ||
| * | ||
| * Includes OpenFeature guides automatically. | ||
| */ | ||
|
|
||
| const https = require('https') | ||
| const fs = require('fs') | ||
| const path = require('path') | ||
|
|
||
| // Keep a single constant for clarity and reuse | ||
| const TREE_URL = 'https://api.github.com/repos/DevCycleHQ/AI-Prompts-And-Rules/git/trees/main?recursive=1' | ||
|
|
||
| function fetchJson(url, headers = {}) { | ||
| const requestHeaders = { | ||
| 'User-Agent': 'devcycle-cli-build-script', | ||
| Accept: 'application/vnd.github+json', | ||
| ...headers, | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| const req = https.get(url, { headers: requestHeaders }, (res) => { | ||
| let data = '' | ||
| res.on('data', (chunk) => (data += chunk)) | ||
| res.on('end', () => { | ||
| if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { | ||
| try { | ||
| resolve(JSON.parse(data)) | ||
| } catch (err) { | ||
| reject(err) | ||
| } | ||
| } else { | ||
| reject(new Error(`HTTP ${res.statusCode}: ${data}`)) | ||
| } | ||
| }) | ||
| }) | ||
| req.on('error', reject) | ||
| req.end() | ||
| }) | ||
| } | ||
|
|
||
| async function main() { | ||
| const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN | ||
| const headers = token ? { Authorization: `Bearer ${token}` } : {} | ||
|
|
||
| const outFile = path.resolve( | ||
| __dirname, | ||
| '..', | ||
| 'src', | ||
| 'mcp', | ||
| 'tools', | ||
| 'installGuides.generated.ts', | ||
| ) | ||
|
|
||
| try { | ||
| const tree = await fetchJson(TREE_URL, headers) | ||
| const all = Array.isArray(tree.tree) ? tree.tree : [] | ||
|
|
||
| // Validate and sanitize file paths returned by GitHub API | ||
| const isValidPath = (filePath) => { | ||
| return ( | ||
| typeof filePath === 'string' && | ||
| filePath.length > 0 && | ||
| !filePath.includes('..') && | ||
| !path.isAbsolute(filePath) && | ||
| filePath.startsWith('install-prompts/') && | ||
| /^[a-zA-Z0-9_.\-\/]+$/.test(filePath) | ||
| ) | ||
| } | ||
|
|
||
| const mdFiles = all | ||
| .filter((item) => item.type === 'blob') | ||
| .map((item) => item.path) | ||
| .filter(isValidPath) | ||
| .filter((p) => p.toLowerCase().endsWith('.md')) | ||
|
|
||
| // Build safe slugs (relative paths within install-prompts without extension) | ||
| const slugSet = new Set() | ||
| for (const p of mdFiles) { | ||
| const raw = p.replace(/^install-prompts\//, '').replace(/\.md$/i, '') | ||
| // extra guards on the slug | ||
| if (!raw || raw.includes('..') || raw.startsWith('/')) continue | ||
| const cleaned = raw | ||
| // allow only safe characters (letters, numbers, dash, underscore, slash) | ||
| .replace(/[^a-zA-Z0-9_\-\/]/g, '') | ||
| // collapse multiple slashes | ||
| .replace(/\/+\/+/g, '/') | ||
| // trim leading/trailing slashes | ||
| .replace(/^\/+|\/+$/g, '') | ||
| if (cleaned) slugSet.add(cleaned) | ||
| } | ||
|
|
||
| const slugs = Array.from(slugSet).sort((a, b) => a.localeCompare(b)) | ||
|
|
||
| const content = `// AUTO-GENERATED BY scripts/fetch-install-prompts.js. DO NOT EDIT. | ||
| export const INSTALL_GUIDES = ${JSON.stringify(slugs, null, 2)} as const | ||
| export type InstallGuideId = typeof INSTALL_GUIDES[number] | ||
| ` | ||
|
|
||
| fs.writeFileSync(outFile, content, 'utf8') | ||
| console.log( | ||
| `Generated ${outFile} with ${slugs.length} install guide entries.`, | ||
| ) | ||
| } catch (err) { | ||
| const message = `[fetch-install-prompts] Failed to generate guides list: ${ | ||
| err && err.message ? err.message : err | ||
| }` | ||
| if (fs.existsSync(outFile)) { | ||
| console.warn( | ||
| `${message}. Existing generated file found at ${outFile}. Proceeding with previously generated data.`, | ||
| ) | ||
| return | ||
| } | ||
| // No previously generated file; fail immediately. | ||
| throw new Error( | ||
| `${message}. No existing generated file found. Cannot proceed.`, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| console.error(err) | ||
| process.exit(1) | ||
| }) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import axios from 'axios' | ||
| import { z } from 'zod' | ||
| import type { IDevCycleApiClient } from '../api/interface' | ||
| import type { DevCycleMCPServerInstance } from '../server' | ||
| import { INSTALL_GUIDES } from './installGuides.generated' | ||
|
|
||
| const InstallGuideArgsSchema = z.object({ | ||
| guide: z.enum(INSTALL_GUIDES), | ||
| }) | ||
|
|
||
| type InstallGuideArgs = z.infer<typeof InstallGuideArgsSchema> | ||
|
|
||
| async function fetchInstallGuideHandler(args: InstallGuideArgs) { | ||
| const trimmedGuide = args.guide.trim().replace(/^\/+|\/+$/g, '') | ||
| const fileName = trimmedGuide.endsWith('.md') | ||
| ? trimmedGuide | ||
| : `${trimmedGuide}.md` | ||
jonathannorris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const repoPath = `install-prompts/${fileName}` | ||
| const sourceUrl = `https://raw.githubusercontent.com/DevCycleHQ/AI-Prompts-And-Rules/main/${repoPath}` | ||
|
|
||
| try { | ||
| const response = await axios.get<string>(sourceUrl, { | ||
| responseType: 'text', | ||
| }) | ||
| return response.data as string | ||
| } catch (error: unknown) { | ||
| const status = axios.isAxiosError(error) | ||
| ? error.response?.status | ||
| : undefined | ||
| if (status === 404) { | ||
| throw new Error( | ||
| `Install guide "${fileName}" not found in install-prompts/. Check the filename (with or without .md).`, | ||
| ) | ||
| } | ||
| throw new Error( | ||
| 'Unable to fetch install guide from GitHub. Please retry.', | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| export function registerInstallTools( | ||
| serverInstance: DevCycleMCPServerInstance, | ||
| ): void { | ||
| serverInstance.registerToolWithErrorHandling( | ||
| 'install_devcycle_sdk', | ||
| { | ||
| description: [ | ||
| 'Fetch DevCycle SDK installation instructions, and follow the instructions to install the DevCycle SDK.', | ||
| "Choose the guide that matches the application's language/framework.", | ||
| ].join('\n'), | ||
| annotations: { | ||
| title: 'Install DevCycle SDK', | ||
| readOnlyHint: true, | ||
| }, | ||
| inputSchema: InstallGuideArgsSchema.shape, | ||
| }, | ||
| async (args: unknown) => { | ||
| const validatedArgs = InstallGuideArgsSchema.parse(args) | ||
| return await fetchInstallGuideHandler(validatedArgs) | ||
| }, | ||
| ) | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.