create-cloudinary-react 1.0.0-beta.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.
@@ -0,0 +1,4 @@
1
+ # Code Owners - Only these people can approve pull requests
2
+ # All files in the repository require Code Owner approval
3
+
4
+ * @strausr
@@ -0,0 +1,45 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ permissions:
7
+ contents: write
8
+ id-token: write
9
+ issues: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ release:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Verify admin permissions
17
+ run: |
18
+ PERMISSION=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
19
+ "https://api.github.com/repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission" \
20
+ | grep -o '"permission":"[^"]*"' | cut -d'"' -f4)
21
+
22
+ if [ "$PERMISSION" != "admin" ]; then
23
+ echo "Error: Only repository admins can trigger releases. Current permission: $PERMISSION"
24
+ exit 1
25
+ fi
26
+
27
+ echo "āœ“ Verified admin permission for ${{ github.actor }}"
28
+
29
+ - uses: actions/checkout@v4
30
+ with:
31
+ fetch-depth: 0
32
+
33
+ - uses: actions/setup-node@v4
34
+ with:
35
+ node-version: '18'
36
+ registry-url: 'https://registry.npmjs.org'
37
+
38
+ - run: npm ci
39
+
40
+ - run: npm test --if-present
41
+
42
+ - name: Release
43
+ env:
44
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45
+ run: npx semantic-release
@@ -0,0 +1 @@
1
+ npx --no -- commitlint --edit ${1}
@@ -0,0 +1,23 @@
1
+ {
2
+ "branches": [
3
+ "main",
4
+ {
5
+ "name": "beta",
6
+ "prerelease": true
7
+ }
8
+ ],
9
+ "plugins": [
10
+ "@semantic-release/commit-analyzer",
11
+ "@semantic-release/release-notes-generator",
12
+ "@semantic-release/changelog",
13
+ "@semantic-release/npm",
14
+ [
15
+ "@semantic-release/git",
16
+ {
17
+ "assets": ["package.json", "CHANGELOG.md"],
18
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
19
+ }
20
+ ],
21
+ "@semantic-release/github"
22
+ ]
23
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - TBD
4
+
5
+ ### Added
6
+ - Initial release
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Cloudinary
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # create-cloudinary-react
2
+
3
+ > **Beta Release** - This is a beta version. We welcome feedback and bug reports!
4
+
5
+ Part of the [Cloudinary Developers](https://github.com/cloudinary-devs) organization.
6
+
7
+ Scaffold a Cloudinary React + Vite + TypeScript project with interactive setup.
8
+
9
+ ## Prerequisites
10
+
11
+ - Node.js 18+ installed
12
+ - A Cloudinary account (free tier available)
13
+ - [Sign up for free](https://cloudinary.com/users/register/free)
14
+ - Your cloud name is in your [dashboard](https://console.cloudinary.com/app/home/dashboard)
15
+
16
+ ## Usage
17
+
18
+ ```bash
19
+ npx create-cloudinary-react
20
+ ```
21
+
22
+ The CLI will prompt you for:
23
+ - Project name
24
+ - **Cloudinary cloud name** (found in your [dashboard](https://console.cloudinary.com/app/home/dashboard))
25
+ - Upload preset (optional - required for uploads, but transformations work without it)
26
+ - AI coding assistant(s) you're using (Cursor, GitHub Copilot, Claude, etc.)
27
+ - Whether to install dependencies
28
+ - Whether to start dev server
29
+
30
+ ## Features
31
+
32
+ - āœ… Interactive setup with validation
33
+ - āœ… Pre-configured Cloudinary React SDK
34
+ - āœ… TypeScript + Vite + React 19
35
+ - āœ… Typed Upload Widget component
36
+ - āœ… Environment variables with VITE_ prefix
37
+ - āœ… Multi-tool AI assistant support (Cursor, GitHub Copilot, Claude, and more)
38
+ - āœ… MCP configuration for Cloudinary integration
39
+ - āœ… ESLint + TypeScript configured
40
+
41
+ ## AI Assistant Support
42
+
43
+ During setup, you'll be asked which AI coding assistant(s) you're using. The CLI will generate the appropriate configuration files:
44
+
45
+ - āœ… **Cursor** → `.cursorrules` + `.cursor/mcp.json` (if selected)
46
+ - āœ… **GitHub Copilot** → `.github/copilot-instructions.md`
47
+ - āœ… **Claude Code / Claude Desktop** → `.claude`, `claude.md` + `.cursor/mcp.json` (if selected)
48
+ - āœ… **Generic AI tools** → `AI_INSTRUCTIONS.md`, `PROMPT.md`
49
+
50
+ **MCP Configuration**: The `.cursor/mcp.json` file is automatically generated if you select Cursor or Claude, as it works with both tools.
51
+
52
+ These rules help AI assistants understand Cloudinary React SDK patterns, common errors, and best practices. The generated app also includes an "AI Prompts" section with ready-to-use suggestions for your AI assistant.
53
+
54
+ ## Development
55
+
56
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/) for version management.
57
+
58
+ ### Commit Format
59
+
60
+ ```
61
+ <type>(<scope>): <subject>
62
+
63
+ <body>
64
+
65
+ <footer>
66
+ ```
67
+
68
+ **Types:**
69
+ - `feat`: New feature
70
+ - `fix`: Bug fix
71
+ - `docs`: Documentation changes
72
+ - `refactor`: Code refactoring
73
+ - `perf`: Performance improvements
74
+ - `chore`: Other changes
75
+
package/cli.js ADDED
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
6
+ import { execSync } from 'child_process';
7
+ import inquirer from 'inquirer';
8
+ import chalk from 'chalk';
9
+ import fs from 'fs-extra';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ const TEMPLATES_DIR = join(__dirname, 'templates');
15
+
16
+ // Validate cloud name format
17
+ function isValidCloudName(name) {
18
+ return /^[a-z0-9_-]+$/.test(name) && name.length > 0;
19
+ }
20
+
21
+ // Validate project name
22
+ function isValidProjectName(name) {
23
+ return /^[a-z0-9_-]+$/i.test(name) && name.length > 0;
24
+ }
25
+
26
+ async function main() {
27
+ console.log(chalk.cyan.bold('\nšŸš€ Cloudinary React + Vite Boilerplate\n'));
28
+ console.log(chalk.gray('šŸ’” Need a Cloudinary account? Sign up for free: https://cloudinary.com/users/register/free\n'));
29
+
30
+ const answers = await inquirer.prompt([
31
+ {
32
+ type: 'input',
33
+ name: 'projectName',
34
+ message: 'Project name:',
35
+ default: 'my-cloudinary-app',
36
+ validate: (input) => {
37
+ if (!input.trim()) {
38
+ return 'Project name cannot be empty';
39
+ }
40
+ if (!isValidProjectName(input)) {
41
+ return 'Project name can only contain letters, numbers, hyphens, and underscores';
42
+ }
43
+ if (existsSync(input)) {
44
+ return `Directory "${input}" already exists. Please choose a different name.`;
45
+ }
46
+ return true;
47
+ },
48
+ },
49
+ {
50
+ type: 'input',
51
+ name: 'cloudName',
52
+ message: 'Cloudinary Cloud Name:',
53
+ description: chalk.gray(
54
+ 'Your cloud name is shown in your dashboard: https://console.cloudinary.com/app/home/dashboard'
55
+ ),
56
+ validate: (input) => {
57
+ if (!input.trim()) {
58
+ return chalk.yellow(
59
+ 'Cloud name is required.\n' +
60
+ ' → Sign up: https://cloudinary.com/users/register/free\n' +
61
+ ' → Find your cloud name: https://console.cloudinary.com/app/home/dashboard'
62
+ );
63
+ }
64
+ if (!isValidCloudName(input)) {
65
+ return 'Cloud name can only contain lowercase letters, numbers, hyphens, and underscores';
66
+ }
67
+ return true;
68
+ },
69
+ },
70
+ {
71
+ type: 'confirm',
72
+ name: 'hasUploadPreset',
73
+ message: 'Do you have an unsigned upload preset? (Required for uploads, optional for transformations)',
74
+ default: false,
75
+ description: chalk.gray(
76
+ 'Upload presets enable client-side uploads. You can set one up later at:\n' +
77
+ 'https://console.cloudinary.com/app/settings/upload/presets'
78
+ ),
79
+ },
80
+ {
81
+ type: 'input',
82
+ name: 'uploadPreset',
83
+ message: 'Upload Preset Name:',
84
+ when: (answers) => answers.hasUploadPreset,
85
+ validate: (input) => {
86
+ if (!input.trim()) {
87
+ return 'Upload preset name cannot be empty';
88
+ }
89
+ return true;
90
+ },
91
+ },
92
+ {
93
+ type: 'checkbox',
94
+ name: 'aiTools',
95
+ message: 'Which AI coding assistant(s) are you using? (Select all that apply)',
96
+ choices: [
97
+ { name: 'Cursor', value: 'cursor' },
98
+ { name: 'GitHub Copilot', value: 'copilot' },
99
+ { name: 'Claude Code / Claude Desktop', value: 'claude' },
100
+ { name: 'Other / Generic AI tools', value: 'generic' },
101
+ ],
102
+ default: ['cursor'],
103
+ },
104
+ {
105
+ type: 'confirm',
106
+ name: 'installDeps',
107
+ message: 'Install dependencies now?',
108
+ default: true,
109
+ },
110
+ {
111
+ type: 'confirm',
112
+ name: 'startDev',
113
+ message: 'Start development server?',
114
+ default: false,
115
+ when: (answers) => answers.installDeps,
116
+ },
117
+ ]);
118
+
119
+ const { projectName, cloudName, uploadPreset, aiTools, installDeps, startDev } = answers;
120
+
121
+ console.log(chalk.blue('\nšŸ“¦ Creating project...\n'));
122
+
123
+ // Create project directory
124
+ const projectPath = join(process.cwd(), projectName);
125
+ mkdirSync(projectPath, { recursive: true });
126
+
127
+ // Template replacement function
128
+ function replaceTemplate(content, vars) {
129
+ let result = content;
130
+ Object.keys(vars).forEach((key) => {
131
+ const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
132
+ result = result.replace(regex, vars[key]);
133
+ });
134
+ return result;
135
+ }
136
+
137
+ // Template variables
138
+ const templateVars = {
139
+ PROJECT_NAME: projectName,
140
+ CLOUD_NAME: cloudName,
141
+ UPLOAD_PRESET: uploadPreset || '',
142
+ };
143
+
144
+ // Function to copy template file
145
+ function copyTemplate(relativePath, outputPath = null) {
146
+ const templatePath = join(TEMPLATES_DIR, relativePath);
147
+ const finalPath = outputPath || join(projectPath, relativePath.replace('.template', ''));
148
+
149
+ // Create directory if needed
150
+ const finalDir = dirname(finalPath);
151
+ if (!existsSync(finalDir)) {
152
+ mkdirSync(finalDir, { recursive: true });
153
+ }
154
+
155
+ if (existsSync(templatePath)) {
156
+ const content = readFileSync(templatePath, 'utf-8');
157
+ const processed = replaceTemplate(content, templateVars);
158
+ writeFileSync(finalPath, processed);
159
+ }
160
+ }
161
+
162
+ // Copy all template files
163
+ const filesToCopy = [
164
+ 'package.json.template',
165
+ 'vite.config.ts.template',
166
+ 'tsconfig.json.template',
167
+ 'tsconfig.app.json.template',
168
+ 'tsconfig.node.json.template',
169
+ 'eslint.config.js.template',
170
+ '.gitignore.template',
171
+ '.env.template',
172
+ 'index.html.template',
173
+ 'README.md.template',
174
+ 'src/cloudinary/config.ts.template',
175
+ 'src/cloudinary/UploadWidget.tsx.template',
176
+ 'src/App.tsx.template',
177
+ 'src/main.tsx.template',
178
+ 'src/index.css.template',
179
+ 'src/App.css.template',
180
+ ];
181
+
182
+ filesToCopy.forEach((file) => {
183
+ copyTemplate(file);
184
+ });
185
+
186
+ // Create AI rules based on user's tool selection
187
+ const aiRulesTemplatePath = join(TEMPLATES_DIR, '.cursorrules.template');
188
+ if (existsSync(aiRulesTemplatePath) && aiTools && aiTools.length > 0) {
189
+ const aiRulesContent = replaceTemplate(
190
+ readFileSync(aiRulesTemplatePath, 'utf-8'),
191
+ templateVars
192
+ );
193
+
194
+ // Generate files based on selected tools
195
+ if (aiTools.includes('cursor')) {
196
+ writeFileSync(join(projectPath, '.cursorrules'), aiRulesContent);
197
+ }
198
+
199
+ if (aiTools.includes('copilot')) {
200
+ const githubDir = join(projectPath, '.github');
201
+ mkdirSync(githubDir, { recursive: true });
202
+ writeFileSync(join(githubDir, 'copilot-instructions.md'), aiRulesContent);
203
+ }
204
+
205
+ if (aiTools.includes('claude')) {
206
+ writeFileSync(join(projectPath, '.claude'), aiRulesContent);
207
+ writeFileSync(join(projectPath, 'claude.md'), aiRulesContent);
208
+ }
209
+
210
+ if (aiTools.includes('generic')) {
211
+ writeFileSync(join(projectPath, 'AI_INSTRUCTIONS.md'), aiRulesContent);
212
+ writeFileSync(join(projectPath, 'PROMPT.md'), aiRulesContent);
213
+ }
214
+
215
+ // Generate MCP configuration if using Cursor or Claude (MCP works with both)
216
+ if (aiTools.includes('cursor') || aiTools.includes('claude')) {
217
+ const mcpTemplatePath = join(TEMPLATES_DIR, '.cursor/mcp.json.template');
218
+ if (existsSync(mcpTemplatePath)) {
219
+ const cursorDir = join(projectPath, '.cursor');
220
+ mkdirSync(cursorDir, { recursive: true });
221
+ const mcpContent = replaceTemplate(
222
+ readFileSync(mcpTemplatePath, 'utf-8'),
223
+ templateVars
224
+ );
225
+ writeFileSync(join(cursorDir, 'mcp.json'), mcpContent);
226
+ }
227
+ }
228
+ }
229
+
230
+ // Copy vite.svg to public directory
231
+ const viteSvgPath = join(projectPath, 'public', 'vite.svg');
232
+ mkdirSync(join(projectPath, 'public'), { recursive: true });
233
+ const viteSvg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>';
234
+ writeFileSync(viteSvgPath, viteSvg);
235
+
236
+ console.log(chalk.green('āœ… Project created successfully!\n'));
237
+
238
+ if (!answers.hasUploadPreset) {
239
+ console.log(chalk.yellow('\nšŸ“ Note: Upload preset not configured'));
240
+ console.log(chalk.gray(' • Transformations will work with sample images'));
241
+ console.log(chalk.gray(' • Uploads require an unsigned upload preset'));
242
+ console.log(chalk.cyan('\n To enable uploads:'));
243
+ console.log(chalk.cyan(' 1. Go to https://console.cloudinary.com/app/settings/upload/presets'));
244
+ console.log(chalk.cyan(' 2. Click "Add upload preset"'));
245
+ console.log(chalk.cyan(' 3. Set it to "Unsigned" mode'));
246
+ console.log(chalk.cyan(' 4. Add the preset name to your .env file\n'));
247
+ }
248
+
249
+ if (installDeps) {
250
+ console.log(chalk.blue('šŸ“¦ Installing dependencies...\n'));
251
+ try {
252
+ process.chdir(projectPath);
253
+ execSync('npm install', { stdio: 'inherit' });
254
+ console.log(chalk.green('\nāœ… Dependencies installed!\n'));
255
+
256
+ if (startDev) {
257
+ console.log(chalk.blue('šŸš€ Starting development server...\n'));
258
+ execSync('npm run dev', { stdio: 'inherit' });
259
+ } else {
260
+ console.log(chalk.cyan(`\nšŸ“ Project created at: ${projectPath}`));
261
+ console.log(chalk.cyan(`\nNext steps:`));
262
+ console.log(chalk.cyan(` cd ${projectName}`));
263
+ console.log(chalk.cyan(` npm run dev\n`));
264
+ }
265
+ } catch (error) {
266
+ console.error(chalk.red('\nāŒ Error installing dependencies:'), error.message);
267
+ console.log(chalk.cyan(`\nYou can install manually:`));
268
+ console.log(chalk.cyan(` cd ${projectName}`));
269
+ console.log(chalk.cyan(` npm install\n`));
270
+ }
271
+ } else {
272
+ console.log(chalk.cyan(`\nšŸ“ Project created at: ${projectPath}`));
273
+ console.log(chalk.cyan(`\nNext steps:`));
274
+ console.log(chalk.cyan(` cd ${projectName}`));
275
+ console.log(chalk.cyan(` npm install`));
276
+ console.log(chalk.cyan(` npm run dev\n`));
277
+ }
278
+ }
279
+
280
+ main().catch((error) => {
281
+ console.error(chalk.red('āŒ Error:'), error.message);
282
+ process.exit(1);
283
+ });
@@ -0,0 +1,23 @@
1
+ export default {
2
+ extends: ['@commitlint/config-conventional'],
3
+ rules: {
4
+ 'type-enum': [
5
+ 2,
6
+ 'always',
7
+ [
8
+ 'feat', // New feature
9
+ 'fix', // Bug fix
10
+ 'docs', // Documentation changes
11
+ 'style', // Code style changes (formatting, etc.)
12
+ 'refactor', // Code refactoring
13
+ 'perf', // Performance improvements
14
+ 'test', // Adding or updating tests
15
+ 'build', // Build system changes
16
+ 'ci', // CI configuration changes
17
+ 'chore', // Other changes that don't modify src or test files
18
+ 'revert', // Revert a previous commit
19
+ ],
20
+ ],
21
+ 'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
22
+ },
23
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "create-cloudinary-react",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "Scaffold a Cloudinary React + Vite + TypeScript project with interactive setup",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-cloudinary-react": "./cli.js"
8
+ },
9
+ "keywords": [
10
+ "cloudinary",
11
+ "react",
12
+ "vite",
13
+ "typescript",
14
+ "boilerplate",
15
+ "scaffold"
16
+ ],
17
+ "author": "",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/cloudinary-devs/create-cloudinary-react.git"
22
+ },
23
+ "homepage": "https://github.com/cloudinary-devs/create-cloudinary-react#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/cloudinary-devs/create-cloudinary-react/issues"
26
+ },
27
+ "dependencies": {
28
+ "inquirer": "^9.2.15",
29
+ "chalk": "^5.3.0",
30
+ "fs-extra": "^11.2.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "scripts": {
36
+ "semantic-release": "semantic-release"
37
+ },
38
+ "devDependencies": {
39
+ "@commitlint/cli": "^19.6.0",
40
+ "@commitlint/config-conventional": "^19.6.0",
41
+ "@semantic-release/changelog": "^6.0.3",
42
+ "@semantic-release/git": "^10.0.1",
43
+ "husky": "^9.1.7",
44
+ "semantic-release": "^23.0.0"
45
+ }
46
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "mcpServers": {
3
+ "cloudinary-asset-mgmt": {
4
+ "url": "https://asset-management.mcp.cloudinary.com/sse"
5
+ },
6
+ "cloudinary-env-config": {
7
+ "url": "https://environment-config.mcp.cloudinary.com/sse"
8
+ }
9
+ }
10
+ }