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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ flow complements existing CLI tools by adding multi-project organization, built-
- **Flexible execution** - Serial, parallel, conditional, and interactive workflows
- **Workflow generation** - Create projects and workflows from reusable templates
- **Composable workflows** - Reference and chain workflows within and across projects
- **Platform integrations** - GitHub Actions, AI assistants (MCP), and more

<p align="center"><img src="docs/_media/demo.gif" alt="flow" width="1600"/></p>

Expand Down
30 changes: 30 additions & 0 deletions cmd/internal/mcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package internal

import (
"github.com/spf13/cobra"

"github.com/flowexec/flow/internal/context"
"github.com/flowexec/flow/internal/logger"
"github.com/flowexec/flow/internal/mcp"
)

func RegisterMCPCmd(ctx *context.Context, rootCmd *cobra.Command) {
subCmd := &cobra.Command{
Use: "mcp",
Short: "Start Model Context Provider (MCP) server for AI assistant integration",
Long: "Start a Model Context Protocol server that enables AI assistants to interact with your flow executables, " +
"workspaces, and configurations through natural language. AI assistants can discover, validate, and execute " +
"flow workflows, making your automation platform accessible through conversational interfaces/clients.\n\n" +
"This server used stdio for transport. For more information on MCP, see https://modelcontextprotocol.io",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) { mcpFunc(ctx, cmd, args) },
}
rootCmd.AddCommand(subCmd)
}

func mcpFunc(_ *context.Context, _ *cobra.Command, _ []string) {
server := mcp.NewServer(&mcp.FlowCLIExecutor{})
if err := server.Run(); err != nil {
logger.Log().FatalErr(err)
}
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,5 @@ func RegisterSubCommands(ctx *context.Context, rootCmd *cobra.Command) {
internal.RegisterTemplateCmd(ctx, rootCmd)
internal.RegisterLogsCmd(ctx, rootCmd)
internal.RegisterSyncCmd(ctx, rootCmd)
internal.RegisterMCPCmd(ctx, rootCmd)
}
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and cross-project composition. Go beyond task running to workflow management tha
- **Cross-Project Composition**: Reference and share workflows between different projects
- **Visual Workflow Browser**: Discover and run workflows with powerful filtering and search
- **Flexible Configuration**: YAML-based definitions with arguments, secrets, and conditional logic
- **Platform Integrations**: GitHub Actions, AI assistants (MCP), and more

**Ready to organize your automation?** → [Install flow](installation.md) → [Quick start guide](quickstart.md)

Expand Down
1 change: 1 addition & 0 deletions docs/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ See https://flowexec.io for more information.
* [flow config](flow_config.md) - Update flow configuration values.
* [flow exec](flow_exec.md) - Execute any executable by reference.
* [flow logs](flow_logs.md) - View execution history and logs.
* [flow mcp](flow_mcp.md) - Start Model Context Provider (MCP) server for AI assistant integration
* [flow secret](flow_secret.md) - Manage secrets stored in a vault.
* [flow sync](flow_sync.md) - Refresh workspace cache and discover new executables.
* [flow template](flow_template.md) - Manage flowfile templates.
Expand Down
31 changes: 31 additions & 0 deletions docs/cli/flow_mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## flow mcp

Start Model Context Provider (MCP) server for AI assistant integration

### Synopsis

Start a Model Context Protocol server that enables AI assistants to interact with your flow executables, workspaces, and configurations through natural language. AI assistants can discover, validate, and execute flow workflows, making your automation platform accessible through conversational interfaces/clients.

This server used stdio for transport. For more information on MCP, see https://modelcontextprotocol.io

```
flow mcp [flags]
```

### Options

```
-h, --help help for mcp
```

### Options inherited from parent commands

```
-L, --log-level string Log verbosity level (debug, info, fatal) (default "info")
--sync Sync flow cache and workspaces
```

### SEE ALSO

* [flow](flow.md) - flow is a command line interface designed to make managing and running development workflows easier.

47 changes: 44 additions & 3 deletions docs/guide/integrations.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,49 @@
# Integrations

flow integrates with popular CI/CD platforms and containerized environments to bring your automation anywhere.
flow integrates with popular CI/CD platforms, AI assistants, and containerized environments to bring your automation anywhere.

## GitHub Actions

## AI Assistant Integration

### Model Context Protocol (MCP) <!-- {docsify-ignore} -->

Connect flow to AI assistants through the local Model Context Protocol server for natural language workflow management.
The flow MCP server enables AI assistants to discover, understand, and execute your flow workflows through conversational interfaces.

#### Basic Usage <!-- {docsify-ignore} -->

Add the MCP server command to your favorite MCP client:

```shell
flow mcp
```

The server uses stdio transport and provides AI assistants with:

**Available Tools:**
- `get_info` - Get flow information, schemas, and current context
- `execute` - Execute flow workflows
- `list_workspaces` - List all registered workspaces
- `get_workspace` - Get details about a specific workspace
- `switch_workspace` - Change the current workspace
- `list_executables` - List and filter executables across workspaces
- `get_executable` - Get detailed information about an executable
- `get_execution_logs` - Retrieve recent execution logs
- `sync_executables` - Sync workspace and executable state

**Available Prompts:**
- `generate_executable` - Generate flow executable configurations
- `generate_project_executables` - Generate complete project automation sets
- `debug_executable` - Debug failing executables
- `migrate_automation` - Convert existing automation to flow
- `explain_flow` - Explain flow concepts and usage

> [!NOTE]
> **Learn more about MCP**: Visit the [Model Context Protocol](https://modelcontextprotocol.io) documentation for client setup and integration details.

## CI/CD & Deployment <!-- {docsify-ignore} -->

### GitHub Actions

Execute flow workflows directly in your GitHub Actions pipelines with the official action.

Expand All @@ -22,7 +63,7 @@ jobs:

> **Complete documentation**: Visit the [Flow Execute Action](https://github.com/marketplace/actions/flow-execute) on GitHub Marketplace.

## Docker
### Docker

Run flow in containerized environments for CI/CD pipelines or isolated execution.

Expand Down
13 changes: 10 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/flowexec/vault v0.1.2
github.com/gen2brain/beeep v0.11.1
github.com/jahvon/glamour v0.8.1-patch3
github.com/mark3labs/mcp-go v0.36.0
github.com/mattn/go-runewidth v0.0.16
github.com/muesli/termenv v0.16.0
github.com/onsi/ginkgo/v2 v2.23.4
Expand Down Expand Up @@ -41,6 +42,8 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymanbagabas/go-udiff v0.2.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/huh v0.7.0 // indirect
Expand All @@ -66,8 +69,10 @@ require (
github.com/gorilla/css v1.0.1 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
Expand All @@ -85,15 +90,17 @@ require (
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/yuin/goldmark v1.7.4 // indirect
github.com/yuin/goldmark-emoji v1.0.3 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/tools v0.34.0 // indirect
)
27 changes: 21 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
Expand Down Expand Up @@ -113,10 +117,13 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o=
github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ=
github.com/jahvon/glamour v0.8.1-patch3 h1:LfyMACZavV8yxK4UsPENNQQOqafWuq4ZdLuEAP2ZLE8=
github.com/jahvon/glamour v0.8.1-patch3/go.mod h1:30MVJwG3rcEHrN277NrA4DKzndSL9lBtEmpcfOygwCQ=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand All @@ -125,6 +132,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
github.com/mark3labs/mcp-go v0.36.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
Expand Down Expand Up @@ -180,8 +191,8 @@ github.com/sergeymakinen/go-ico v1.0.0-beta.0 h1:m5qKH7uPKLdrygMWxbamVn+tl2HfiA3
github.com/sergeymakinen/go-ico v1.0.0-beta.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
Expand All @@ -196,8 +207,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
Expand All @@ -213,8 +228,8 @@ golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -226,8 +241,8 @@ golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
48 changes: 4 additions & 44 deletions internal/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/flowexec/tuikit"
"github.com/flowexec/tuikit/themes"
Expand Down Expand Up @@ -67,13 +65,7 @@ func NewContext(ctx context.Context, stdIn, stdOut *os.File) *Context {
}

workspaceCache := cache.NewWorkspaceCache()
if workspaceCache == nil {
panic("workspace cache initialization error")
}
executableCache := cache.NewExecutableCache(workspaceCache)
if executableCache == nil {
panic("executable cache initialization error")
}

ctxx, cancel := context.WithCancel(ctx)
c := &Context{
Expand Down Expand Up @@ -189,43 +181,11 @@ func ExpandRef(ctx *Context, ref executable.Ref) executable.Ref {
}

func currentWorkspace(cfg *config.Config) (*workspace.Workspace, error) {
var ws, wsPath string
mode := cfg.WorkspaceMode

switch mode {
case config.ConfigWorkspaceModeDynamic:
wd, err := os.Getwd()
if err != nil {
return nil, err
}
if runtime.GOOS == "darwin" {
// On macOS, paths that start with /tmp (and some other system directories)
// are actually symbolic links to paths under /private. The OS may return
// either form of the path - e.g., both "/tmp/file" and "/private/tmp/file"
// refer to the same location. We strip the "/private" prefix for consistent
// path comparison, while preserving the original paths for filesystem operations.
wd = strings.TrimPrefix(wd, "/private")
}

for wsName, path := range cfg.Workspaces {
rel, err := filepath.Rel(filepath.Clean(path), filepath.Clean(wd))
if err != nil {
return nil, err
}
if !strings.HasPrefix(rel, "..") {
ws = wsName
wsPath = path
break
}
}
fallthrough
case config.ConfigWorkspaceModeFixed:
if ws != "" && wsPath != "" {
break
}
ws = cfg.CurrentWorkspace
wsPath = cfg.Workspaces[ws]
ws, err := cfg.CurrentWorkspaceName()
if err != nil {
return nil, err
}
wsPath := cfg.Workspaces[ws]
if ws == "" || wsPath == "" {
return nil, fmt.Errorf("current workspace not found")
}
Expand Down
40 changes: 40 additions & 0 deletions internal/mcp/command_executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package mcp

import (
"os"
"os/exec"

"github.com/pkg/errors"
)

const cliBinaryEnvKey = "FLOW_CLI_BINARY"

//go:generate mockgen -destination=mocks/command_executor.go -package=mocks . CommandExecutor
type CommandExecutor interface {
Execute(args ...string) (string, error)
}

// FlowCLIExecutor runs the flow CLI with provided arguments. The CLI is being executed instead of importing the
// internal flow package directly to avoid duplicating the code that's defined in the cmd package, to make testing
// easier, and to avoid having to refactor the Context obj which is not currently designed in a way to be copied/reused
// across "executions". Maybe consider refactoring this when the context is refactored.
//
// The binary name can be overridden by setting the FLOW_CLI_BINARY environment variable.
type FlowCLIExecutor struct{}

func (c *FlowCLIExecutor) Execute(args ...string) (string, error) {
name := "flow"
if envName := os.Getenv(cliBinaryEnvKey); envName != "" {
name = envName
}
cmd := exec.Command(name, args...)
output, err := cmd.CombinedOutput()
if err != nil {
// Only return an error if it's not an exit error.
exitErr := &exec.ExitError{}
if !errors.As(err, &exitErr) {
return string(output), err
}
}
return string(output), nil
}
Loading