Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 22 additions & 53 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ All-in-one command to get started quickly. This command combines `init`, feature
and type generation in a single step. Use this for the fastest way to get up and running with Bucket.

```bash
npx bucket new "My Feature" [--key my-feature] [--app-id ap123456789] [--key-format custom] [--out gen/features.ts] [--format react]
npx bucket new "My Feature" [--app-id ap123456789] [--key my-feature] [--key-format custom] [--out gen/features.ts] [--format react]
```

Options:
Expand Down Expand Up @@ -138,7 +138,7 @@ Create a new feature in your Bucket app.
The command guides you through the feature creation process with interactive prompts if options are not provided.

```bash
npx bucket features create "My Feature" [--key my-feature] [--app-id ap123456789] [--key-format custom]
npx bucket features create "My Feature" [--app-id ap123456789] [--key my-feature] [--key-format custom]
```

Options:
Expand Down Expand Up @@ -205,7 +205,7 @@ Grant or revoke access to specific features for companies, segments, and users.
If no feature key is provided, you'll be prompted to select one from a list.

```bash
npx bucket companies features access [featureKey] [--enable|--disable] [--companies <id...>] [--segments <id...>] [--users <id...>] [--app-id ap123456789]
npx bucket companies features access [--app-id ap123456789] [featureKey] [--enable|--disable] [--companies <id...>] [--segments <id...>] [--users <id...>]
```

Arguments:
Expand Down Expand Up @@ -252,7 +252,7 @@ Bucket provides powerful AI-assisted development capabilities through rules and
The `rules` command helps you set up AI-specific rules for your project. These rules enable AI tools to better understand how to work with Bucket and feature flags and how they should be used in your codebase.

```bash
npx bucket rules [--format cursor|copilot] [--yes]
npx bucket rules [--format <cursor|copilot>] [--yes]
```

Options:
Expand All @@ -266,67 +266,36 @@ This command will add rules to your project that provide AI tools with context a

## Model Context Protocol

The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Bucket, MCP enables your development environment to understand your feature flags, their states, and their relationships within your codebase. This creates a seamless bridge between your feature management workflow and AI-powered development tools. MCP is in a very early stage of development and changes are frequent, if something isn't working please check out the [Model Context Protocol Website](https://modelcontextprotocol.io/) and open an [issue ticket here](https://github.com/bucketco/bucket-javascript-sdk/issues).
The Model Context Protocol (MCP) is an open protocol that provides a standardized way to connect AI models to different data sources and tools. In the context of Bucket, MCP enables your code editor to understand your feature flags, their states, and their relationships within your codebase. This creates a seamless bridge between your feature management workflow and AI-powered development tools. The MCP server is hosted by Bucket, so it's very easy to get started.

_\*\*Note: The Bucket `mcp` CLI command was previously used for a \_local_ server. However, in recent versions of the Bucket CLI, the `mcp` command has been repurposed to help you connect to the new remote MCP server.\*\*\_

### Setting up MCP

MCP servers currently run locally on your machine. To start the MCP server run the CLI command from your Bucket initialized project directory:
The `mcp` command helps you configure your editor or AI client to connect with Bucket's remote MCP server. This allows your AI tools to understand your feature flags and provide more contextual assistance.

```bash
npx bucket mcp [--port <number|"auto">] [--app-id ap123456789]
npx bucket mcp [--app-id <id>] [--editor <editor>] [--scope <local|global>]
```

Options:

- `--port`: Port to run the SSE server on (defaults to 8050, "auto" for random port).
- `--app-id`: App ID to use.

This will start an SSE server at `http://localhost:8050/sse` by default which you can connect to using your [client of choice](https://modelcontextprotocol.io/clients). Below are examples that work for [Cursor IDE](https://www.cursor.com/) and [Claude Desktop](https://claude.ai/download).

#### Server-Side Events (SSE)

```json
{
"mcpServers": {
"Bucket": {
"url": "http://localhost:8050/sse"
}
}
}
```

#### STDIO Proxy

Some clients don't support SSE and can instead interface with the MCP server over a STDIO proxy.

```json
{
"mcpServers": {
"Bucket": {
"command": "npx",
"args": ["-y", "supergateway", "--sse", "http://localhost:8050/sse"]
}
}
}
```

### Cursor IDE

To enable MCP features in [Cursor IDE](https://www.cursor.com/):

1. Open Cursor IDE.
2. Go to `Settings > MCP`.
3. Click `Add new global MCP server` and paste the `SSE` config.
4. Save and go back to Cursor.
- `--app-id`: App ID to use for the MCP connection.
- `--editor`: The editor/client to configure:
- `cursor`: [Cursor IDE](https://www.cursor.com/)
- `vscode`: [Visual Studio Code](https://code.visualstudio.com/)
- `claude`: [Claude Desktop](https://claude.ai/download)
- `windsurf`: [Windsurf](https://windsurf.com/editor)
- `--scope`: Whether to configure settings globally or locally for the project.

### Claude Desktop
The command will guide you through:

To enable MCP features in [Claude Desktop](https://claude.ai/download):
1. Selecting which editor/client to configure.
2. Choosing which Bucket app to connect to.
3. Deciding between global or project-local configuration.
4. Setting up the appropriate configuration file for your chosen editor .

1. Open Claude Desktop.
2. Go to `Settings > Developer`.
3. Click `Edit config` and paste the `STDIO` config.
4. Save and restart Claude Desktop.
_**Note: The setup uses [mcp-remote](https://github.com/geelen/mcp-remote) as a compatibility layer allowing the remote hosted Bucket MCP server to work with all editors/clients that support MCP STDIO servers. If your editor/client supports HTTP Streaming with OAuth you can connect to the Bucket MCP server directly.**_

## Development

Expand Down
13 changes: 7 additions & 6 deletions packages/cli/commands/companies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
MissingEnvIdError,
} from "../utils/errors.js";
import { appIdOption, companyFilterOption } from "../utils/options.js";
import { baseUrlSuffix } from "../utils/path.js";
import { baseUrlSuffix } from "../utils/urls.js";

export const listCompaniesAction = async (options: { filter?: string }) => {
const { baseUrl, appId } = configStore.getConfig();
Expand All @@ -20,13 +20,14 @@ export const listCompaniesAction = async (options: { filter?: string }) => {
if (!appId) {
return handleError(new MissingAppIdError(), "Companies List");
}
const app = getApp(appId);
const production = app.environments.find((e) => e.isProduction);
if (!production) {
return handleError(new MissingEnvIdError(), "Companies List");
}

try {
const app = getApp(appId);
const production = app.environments.find((e) => e.isProduction);
if (!production) {
return handleError(new MissingEnvIdError(), "Companies List");
}

spinner = ora(
`Loading companies for app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}...`,
).start();
Expand Down
42 changes: 30 additions & 12 deletions packages/cli/commands/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Argument, Command } from "commander";
import { relative } from "node:path";
import ora, { Ora } from "ora";

import { getApp, getOrg } from "../services/bootstrap.js";
import { App, getApp, getOrg } from "../services/bootstrap.js";
import {
createFeature,
Feature,
Expand Down Expand Up @@ -36,7 +36,7 @@ import {
typesOutOption,
userIdsOption,
} from "../utils/options.js";
import { baseUrlSuffix, featureUrl } from "../utils/path.js";
import { baseUrlSuffix, featureUrl } from "../utils/urls.js";

const lf = new Intl.ListFormat("en");

Expand All @@ -54,7 +54,14 @@ export const createFeatureAction = async (
if (!appId) {
return handleError(new MissingAppIdError(), "Features Create");
}
const app = getApp(appId);

let app: App;
try {
app = getApp(appId);
} catch (error) {
return handleError(error, "Features Create");
}

const production = app.environments.find((e) => e.isProduction);

try {
Expand Down Expand Up @@ -102,13 +109,13 @@ export const listFeaturesAction = async () => {
if (!appId) {
return handleError(new MissingAppIdError(), "Features Create");
}
const app = getApp(appId);
const production = app.environments.find((e) => e.isProduction);
if (!production) {
return handleError(new MissingEnvIdError(), "Features Types");
}

try {
const app = getApp(appId);
const production = app.environments.find((e) => e.isProduction);
if (!production) {
return handleError(new MissingEnvIdError(), "Features Types");
}
spinner = ora(
`Loading features of app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}...`,
).start();
Expand Down Expand Up @@ -142,7 +149,13 @@ export const generateTypesAction = async () => {
return handleError(new MissingAppIdError(), "Features Types");
}

const app = getApp(appId);
let app: App;
try {
app = getApp(appId);
} catch (error) {
return handleError(error, "Features Types");
}

const production = app.environments.find((e) => e.isProduction);
if (!production) {
return handleError(new MissingEnvIdError(), "Features Types");
Expand All @@ -161,8 +174,7 @@ export const generateTypesAction = async () => {
);
} catch (error) {
spinner?.fail("Loading features failed.");
void handleError(error, "Features Types");
return;
return handleError(error, "Features Types");
}

try {
Expand Down Expand Up @@ -202,7 +214,13 @@ export const featureAccessAction = async (
return handleError(new MissingAppIdError(), "Feature Access");
}

const app = getApp(appId);
let app: App;
try {
app = getApp(appId);
} catch (error) {
return handleError(error, "Features Types");
}

const production = app.environments.find((e) => e.isProduction);
if (!production) {
return handleError(new MissingEnvIdError(), "Feature Access");
Expand Down
11 changes: 3 additions & 8 deletions packages/cli/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const initAction = async (args: InitArgs = {}) => {

// Load apps
spinner = ora(`Loading apps from ${chalk.cyan(baseUrl)}...`).start();
apps = await listApps();
apps = listApps();
spinner.succeed(`Loaded apps from ${chalk.cyan(baseUrl)}.`);
} catch (error) {
spinner?.fail("Loading apps failed.");
Expand All @@ -42,20 +42,15 @@ export const initAction = async (args: InitArgs = {}) => {

try {
let appId: string | undefined;
const nonDemoApps = apps.filter((app) => !app.demo);
const nonDemoApp = apps.find((app) => !app.demo);

// If there is only one non-demo app, select it automatically
if (apps.length === 0) {
throw new Error("You don't have any apps yet. Please create one.");
} else if (nonDemoApps.length === 1) {
appId = nonDemoApps[0].id;
console.log(
`Automatically selected app ${chalk.cyan(nonDemoApps[0].name)} (${chalk.cyan(appId)}).`,
);
} else {
const longestName = Math.max(...apps.map((app) => app.name.length));
appId = await select({
message: "Select an app",
default: nonDemoApp?.id,
choices: apps.map((app) => ({
name: `${app.name.padEnd(longestName, " ")}${app.demo ? " [Demo]" : ""}`,
value: app.id,
Expand Down
Loading