# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- docs/oplink-docs/README.md ---
# oplink Documentation

This repository contains the official documentation for oplink, a modern 3D game starter kit for the web.

## Setup

Make sure to install dependencies:

```bash
# npm
npm install

# pnpm
pnpm install

# yarn
yarn install

# bun
bun install
```

## Development Server

Start the development server on `http://localhost:3000`:

```bash
# npm
npm run dev

# pnpm
pnpm dev

# yarn
yarn dev

# bun
bun run dev
```

## Production

Build the application for production:

```bash
# npm
npm run build

# pnpm
pnpm build

# yarn
yarn build

# bun
bun run build
```

Locally preview production build:

```bash
# npm
npm run preview

# pnpm
pnpm preview

# yarn
yarn preview

# bun
bun run preview
```

## Demo

Experience oplink in action at our live demo: [viber3d-spacewars.kevinkern.dev](https://viber3d-spacewars.kevinkern.dev/)

## Documentation Structure

The documentation is organized into the following sections:

- **Introduction**: Overview of oplink and its features
- **Getting Started**: Installation and basic usage
- **Core Concepts**: Entity Component System, Entities, Traits, Systems, and Components
- **Systems**: Detailed information about built-in systems

## Contributing

Contributions to the documentation are welcome! Please follow these steps:

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request

## License

oplink and its documentation are licensed under the MIT License.


## Links discovered
- [viber3d-spacewars.kevinkern.dev](https://viber3d-spacewars.kevinkern.dev/)

--- docs/oplink-docs/content/6.contributing/0-architecture-overview.md ---
---
title: Architecture Overview
description: High‑level architecture for contributors
---

This page gives contributors a quick mental model of how Oplink fits together. For the full diagram, see docs/arch.md in the repo.

## Big Picture

- IDE (MCP client) connects to the Oplink stdio server.
- Oplink loads workflows from `.mcp-workflows/workflows.yaml` and external servers from `.mcp-workflows/servers.json`.
- `.env` in the same folder is auto‑loaded and passed to external processes (e.g., Docker `-e VAR`).
- Oplink orchestrates tool calls; `mcporter` handles discovery/auth/transport to external MCP servers.

```text
IDE → Oplink (workflows + registry + .env) → mcporter → external MCP servers → Oplink → IDE
```

## Authoring Model

- Three workflow shapes (runtime optional; schema enforces shapes):
  - Scripted: requires `steps`, disallows `externalServers`.
  - External: requires `externalServers` + `prompt`, disallows `steps`.
  - Prompt‑only: requires `prompt`, disallows `steps`/`externalServers`.
- Step keys: `call`, `args`, `saveAs`, `requires`, `quiet`.

## One Source of Truth

- Keep external server definitions in `.mcp-workflows/servers.json` only.
- IDE configs (.cursor/mcp.json, etc.) should point only to Oplink — don’t duplicate external servers in IDE files.

## Env & Examples (Atlassian)

- Cloud: `JIRA_USERNAME` + `JIRA_API_TOKEN` (and Confluence equivalents).
- Server/Data Center: `JIRA_PERSONAL_TOKEN` (and `CONFLUENCE_PERSONAL_TOKEN`), optional `*_SSL_VERIFY=false`.
- Place `.env` inside the `--config` directory so Oplink auto‑loads it and passes env to the container.

## Errors & Suggestions

- Unknown tool names include "Did you mean …" suggestions based on the live catalog.
- External call failures include alias:tool and step info (for scripted flows).

## Reproducibility (Planned)

- Catalog lockfile and `oplink doctor/verify` will make large registries reproducible and debuggable.

See also: repo file `docs/arch.md` for the Mermaid diagram and more detail.

## Architecture Diagram

![Oplink Architecture Diagram](/architecture-diagram.svg)

**Notes:**
- Only workflow tools are exposed to the MCP client; helper tools stay internal.
- mcporter handles discovery, auth, stdio/http transport, retries, and connection reuse.
- `.env` is auto‑loaded from the `--config` directory and passed to external servers (e.g., Docker `-e`).


## Links discovered
- [Oplink Architecture Diagram](https://github.com/regenrek/oplink-mcp/blob/main/architecture-diagram.svg)

--- docs/oplink-docs/content/3.workflows/1-overview.md ---
---
title: Workflows Overview
description: How workflows work in Oplink and when to use each mode
navigation.icon: i-heroicons-arrows-right-left
---

Oplink workflows are YAML-defined tools that your IDE/agent can call via MCP. They let you combine prompts and external MCP servers into reusable flows that your team can version, review, and share.

Why workflows

- Combine prompts with MCP tools into one invokable command
- Share a consistent toolbox across teammates/repos
- Curate arguments, defaults, and sequences for reliability

Project setup

- `npx oplink@latest init`
- Add `.mcp-workflows/workflows.yaml` (and `servers.json` if using external servers)
- Point your IDE at `--config ./.mcp-workflows` when starting the server

Workflow modes

- Prompt workflows: a single prompt tool (great for heuristics, helper text)
- Auto-discovery workflows: route calls to external servers; Oplink registers `describe_tools` so agents can discover available commands using cached schemas
- Scripted workflows: multi-step orchestration that validates args and calls specific external tools in sequence

Folder layout

- `.mcp-workflows/workflows.yaml` – your workflow definitions
- `.mcp-workflows/servers.json` – registry of external MCP servers (aliases, commands, env)
- `.mcp-workflows/.tokens/*` – OAuth tokens/cache (ignored by git)

Next steps

- [Auto‑Discovery Workflows](./2-auto-discovery)
- [Scripted Workflows](./3-scripted)
- [External Servers & Registry](./4-external-servers)
- [Parameters & Schemas](./5-parameters-and-schemas)
- [Debugging & Caching](./6-debugging-and-caching)
- See the Examples section for practical workflow examples


## Links discovered
- [Auto‑Discovery Workflows](https://github.com/regenrek/oplink-mcp/blob/main/docs/oplink-docs/content/3.workflows/2-auto-discovery.md)
- [Scripted Workflows](https://github.com/regenrek/oplink-mcp/blob/main/docs/oplink-docs/content/3.workflows/3-scripted.md)
- [External Servers & Registry](https://github.com/regenrek/oplink-mcp/blob/main/docs/oplink-docs/content/3.workflows/4-external-servers.md)
- [Parameters & Schemas](https://github.com/regenrek/oplink-mcp/blob/main/docs/oplink-docs/content/3.workflows/5-parameters-and-schemas.md)
- [Debugging & Caching](https://github.com/regenrek/oplink-mcp/blob/main/docs/oplink-docs/content/3.workflows/6-debugging-and-caching.md)

--- docs/oplink-docs/content/1.getting-started/1.index.md ---
---
title: Introduction
description: Intro to the OPLINK docs
navigation.icon: i-lucide-house
---

Welcome to **OPLINK**, an MCP server for workflows. It sits between your IDE and multiple MCP servers, reads simple YAML workflow definitions, and exposes them as a single, well‑structured tool surface in your editor.

Model Context Protocol (MCP) lets AI agents call tools directly from your editor. But as soon as you add more than one MCP server, things get messy:

- Each server has its own tools, names, and auth quirks.
- Prompts and workflows end up copy‑pasted into different projects.
- There’s no single place to version and share “how we actually debug, triage, or investigate things.”

OPLINK fixes this by acting as the **workflow hub**:

- You declare workflows in `.mcp-workflows/workflows.yaml`.
- Oplink runs as an MCP server (`oplink server --config ./.mcp-workflows`).
- Your IDE/agent sees one MCP endpoint with curated tools (your workflows), not a pile of raw servers.

## Key Features

::card-group

::card
---
title: Workflow Orchestration
icon: i-lucide-git-branch
---
Combine multiple MCP servers and prompts into powerful, reusable workflows
::

::card
---
title: Custom Commands
icon: i-lucide-terminal
---
Trigger workflows with intuitive commands like "enter debugger mode" or "use thinking mode"
::

::card
---
title: Version Control
icon: i-lucide-git-merge
---
Share and version control your workflows through simple YAML files
::

::card
---
title: Smart Strategies
icon: i-lucide-brain
---
Define custom strategies for using tools sequentially or situationally
::

::card
---
title: Team Onboarding
icon: i-lucide-users
---
Easily onboard your team to best practices with versioned workflow configurations
::

::card
---
title: Examples Library
icon: i-lucide-library
---
Curated example workflows for common development tasks
::

::

## Why OPLINK?

OPLINK was born from the realization that “just add more MCP servers” doesn’t scale on its own. The valuable part is **how** you sequence and combine those tools for real tasks: debugging, incident response, doc lookup, triage, migrations, etc.

By putting workflows in one MCP server, OPLINK helps you:

- Standardize development practices with named workflows (`frontend_debugger`, `deepwiki_lookup`, etc.)
- Reduce cognitive load when switching between tasks (agents and humans see a small, meaningful surface)
- Make AI tools more accessible and practical for your team
- Improve collaboration and knowledge sharing through versioned configs
- Accelerate development cycles with reusable, reviewed flows

Whether you're a solo developer or part of a large team, OPLINK turns your MCP ecosystem into a coherent, versioned set of workflows instead of a bag of individual servers.

## Thanks

Special thanks to:

- [Peter Steinberger](https://github.com/steipete) for bringing in and maintaining [mcporter](https://github.com/steipete/mcporter), which powers Oplink’s external MCP discovery and transport.
- [Ted Werbel](https://github.com/tedjames) with which I've started this project originally in early 2025 as MCPN. It gave a base idea what oplink is today


## Links discovered
- [Peter Steinberger](https://github.com/steipete)
- [mcporter](https://github.com/steipete/mcporter)
- [Ted Werbel](https://github.com/tedjames)

--- docs/oplink-docs/content/1.getting-started/2.installation.md ---
---
title: Installation
description: Installing OPLINK
navigation.icon: i-lucide-download
---

OPLINK is an MCP server you run alongside your editor. It reads workflows from a `.mcp-workflows` directory and exposes them as tools to your IDE/agent.

There are two typical ways to install it:

### Path A – MCP server only (most users)

If you just want to *use* workflows (not scaffold templates), you can:

1. Create a `.mcp-workflows` directory in your project.
   - Add `.mcp-workflows/workflows.yaml` (and `servers.json` if using external servers).
   - Add `.mcp-workflows/.env` if you need environment variables (API keys, tokens, etc.).
2. Point your IDE or MCP client at:

   ```bash
   npx -y oplink@latest server --config ./.mcp-workflows
   ```

You never have to run `init` if you're comfortable creating those files yourself.

**Note:** The `.env` file should be placed inside the `.mcp-workflows` directory (the same directory you pass to `--config`). Oplink automatically loads `.env` files from this directory before expanding `${VAR}` placeholders in `servers.json`.

> **⚠️ Security Warning:** Never commit `.env` files to version control. Add `.mcp-workflows/.env` to your `.gitignore` file to prevent accidentally committing sensitive credentials.

### Path B – CLI + templates (full authoring)

If you want the CLI helpers (`init`, `validate`, `add` examples, bundled JSON Schemas), run:

```bash
npx -y oplink@latest init
```

This will:

1. Create a `.mcp-workflows` directory (if needed).
2. Scaffold `workflows.yaml` and `servers.json` examples.
3. Copy JSON Schemas into `schema/` for editor validation.

After that:

1. Create `.mcp-workflows/.env` if you need environment variables (API keys, tokens, etc.).
2. Commit `.mcp-workflows` (and `schema/` if you want schema validation in CI). **Do not commit `.env` files** — add them to `.gitignore`.
3. Point your IDE entry to `--config ./.mcp-workflows`.

**Note:** The `.env` file must be placed inside the `.mcp-workflows` directory (the same directory you pass to `--config`). Oplink automatically loads `.env` files from this directory before expanding `${VAR}` placeholders in `servers.json`.

> **⚠️ Security Warning:** Never commit `.env` files to version control. Add `.mcp-workflows/.env` to your `.gitignore` file to prevent accidentally committing sensitive credentials.

## Table of Contents

- [Requirements](#requirements)
- [Choose Your Path](#choose-your-path)
- [Install in Your IDE](#install-in-your-ide)
  - [Cursor](#cursor)
  - [Codex CLI](#codex-cli)
  - [Claude Code](#claude-code)
  - [Cline](#cline)
  - [GitHub Copilot (VS Code)](#github-copilot-vs-code)

## Install in Your IDE

Use these minimal, copy‑pasteable setups. Each IDE section is collapsible.

<details>
  <summary id="cursor"><strong>Cursor</strong></summary>

Cursor supports MCP via a global or project `mcp.json`.

Paths:

- macOS/Linux: `~/.cursor/mcp.json`
- Windows: `%USERPROFILE%\\.cursor\\mcp.json`
- Project: `.cursor/mcp.json` in your repo

Run with a local workflows directory:

```json
{
  "mcpServers": {
    "oplink": {
      "command": "npx",
      "args": ["oplink@latest", "server", "--config", "./.mcp-workflows"],
      "env": {}
    }
  }
}
```

</details>

<details>
  <summary id="codex-cli"><strong>Codex CLI</strong></summary>

Add Oplink as an MCP server:

```bash
codex mcp add oplink -- npx -y oplink@latest server --config /absolute/path/to/.mcp-workflows
```

</details>

<details>
  <summary id="claude-code"><strong>Claude Code</strong></summary>

Register via CLI:

```bash
claude mcp add oplink -- npx -y oplink@latest server --config /absolute/path/to/.mcp-workflows
```

Alternatively with JSON:

```bash
claude mcp add-json oplink '{"type":"stdio","command":"npx","args":["oplink@latest","server","--config","/absolute/path/to/.mcp-workflows"]}'
```

</details>

<details>
  <summary id="cline"><strong>Cline (VS Code/Cursor)</strong></summary>

Cline uses `cline_mcp_settings.json`.

Example:

```json
{
  "mcpServers": {
    "oplink": {
      "command": "npx",
      "args": ["oplink@latest", "server", "--config", "./.mcp-workflows"],
      "transportType": "stdio",
      "timeout": 60,
      "env": {}
    }
  }
}
```

</details>

<details>
  <summary id="github-copilot-vs-code"><strong>GitHub Copilot (VS Code)</strong></summary>

Workspace `.vscode/mcp.json` (stdio):

```json
{
  "servers": {
    "oplink": {
      "type": "stdio",
      "command": "npx",
      "args": ["oplink@latest", "server", "--config", "./.mcp-workflows"]
    }
  }
}
```

</details>

## Additional Options

To add example workflows from a remote (giget) source:

```bash
npx oplink@latest add github:user/repo/path#ref
```

## Editor IntelliSense (optional, recommended)

Enable completions/validation for your YAML files:

- Add a modeline at the top of `.mcp-workflows/workflows.yaml`:
  ```yaml
  # yaml-language-server: $schema=../schema/oplink-workflows.schema.json
  ```
- Add `$schema` to `.mcp-workflows/servers.json`:
  ```jsonc
  { "$schema": "../schema/oplink-servers.schema.json", "servers": { /* ... */ } }
  ```
- Or map schemas in VS Code workspace settings:
  ```json
  {
    "yaml.schemas": {
      "./schema/oplink-workflows.schema.json": ".mcp-workflows/workflows.yaml",
      "./schema/oplink-servers.schema.json": ".mcp-workflows/servers.json"
    }
  }
  ```


--- docs/oplink-docs/content/1.getting-started/3.cli.md ---
---
title: CLI
description: Overview of OPLINK CLI usage
navigation.icon: i-heroicons-command-line
---

The OPLINK CLI lets you scaffold configs and run the MCP server that exposes your workflows to IDEs/agents.

## Quick Reference

- `oplink init` – create a minimal project config and `.mcp-workflows` folder
- `oplink add <giget>` – fetch example workflows from a remote source
- `oplink server` – start the MCP stdio server (used by IDE MCP integrations)
- `oplink config get` – print the resolved `oplink.config.mjs`
 - `oplink validate` – validate `.mcp-workflows/workflows.yaml` and `servers.json`
 - `oplink scaffold workflow <name>` – append a new prompt or scripted workflow block

Tip: run any command with `--help` to see flags.

## Commands

- **`oplink init`**
  Initialize Oplink in a project. Creates `.mcp-workflows/` and `oplink.config.mjs` if missing.
  ```bash
  npx oplink init
  ```
  Options:
  - `--dir <directory>` – target directory (default `.`)
  - `--ide <cursor|windsurf|cline|rootcode|headless>` – prefill IDE hints (defaults to auto‑detect)
  - `--cwd <directory>` – working directory (advanced)

- **`oplink add <giget>`**
  Pull workflows from a remote (giget) source into `.mcp-workflows`.
  ```bash
  npx oplink add github:user/repo/path
  ```
  Options:
  - `--force` – overwrite existing file/directory
  - `--cwd <dir>` – custom working directory

- **`oplink server`**
  Start the MCP stdio server using your project workflows.
  ```bash
  npx oplink server --config ./.mcp-workflows
  ```
  Options:
  - `--config <path>` – directory named `.mcp-workflows` or `.workflows`
  - `--cwd <dir>` – working directory
  - `--logLevel <silent|info|verbose>` – control diagnostic output

- **`oplink config get`**
  Print the resolved Oplink config (if present).
  ```bash
  npx oplink config get
  ```

- **`oplink validate`**
  Validate your `.mcp-workflows` files against the official schemas.
  ```bash
  npx oplink validate --config ./.mcp-workflows
  ```

- **`oplink scaffold workflow <name>`**
  Append a valid workflow block (prompt by default, add `--scripted` for steps).
  ```bash
  npx oplink scaffold workflow take_screenshot --scripted
  ```

## Examples

1. **Initialize in `my-project`**:
   ```bash
   npx oplink init --dir my-project
   ```
2. **Add from a giget source**:
   ```bash
   npx oplink add github:username/workflows/coding
   ```
3. **Run against a project workflows directory**:
   ```bash
   npx oplink server --config ./.mcp-workflows
   ```

## Environment & .env

- Oplink automatically loads `.env` files from your `--config` directory before expanding `${VAR}` in `.mcp-workflows/servers.json`.
  - Precedence: shell > `.env.{NODE_ENV}.local` > `.env.{NODE_ENV}` > `.env.local` > `.env`.
  - Shell‑exported values always win (they are never overridden by files).
- You can still export variables in your shell or configure env in your IDE’s MCP entry; those take precedence over `.env`.
- After loading `.env`, any unresolved `${VAR}` placeholders will cause startup to fail with a clear error.

## Notes

- The server speaks MCP JSON‑RPC on STDOUT and writes diagnostics to STDERR, so keep STDOUT clean in custom scripts.
- There is no `oplink run` command; IDEs call tools through the MCP server you start with `oplink server`.

For more details, see [Installation](./2.installation) and [Workflows](/workflows/1-overview).


## Links discovered
- [Installation](https://github.com/regenrek/oplink-mcp/blob/main/docs/oplink-docs/content/1.getting-started/2.installation)
- [Workflows](https://github.com/regenrek/oplink-mcp/blob/main/workflows/1-overview.md)

--- docs/oplink-docs/content/4.examples/1-linear-discord.md ---
---
title: Linear + Discord Integration
description: Integrate Linear issue management and Discord messaging workflows
---

This example demonstrates how to integrate Linear (issue management) and Discord (messaging) MCP servers into Oplink workflows. You can fetch Linear issues and send Discord notifications to assignees.

## Prerequisites

### Linear Setup

Linear's hosted MCP server uses OAuth 2.1. mcporter handles the browser/device flow automatically.

1. Run the bootstrap helper:
   ```bash
   pnpm bootstrap:linear
   ```
   - The script copies `servers.json.example` → `servers.json` and ensures `.tokens/linear` exists
   - Skip prompts to rely on Linear's dynamic registration—mcporter will open a browser the first time a workflow calls a Linear tool

2. (Optional) Create a reusable OAuth client under **Linear → Settings → API** and paste the client ID/secret when prompted

### Discord Setup

1. **Create a Discord Bot:**
   - Go to [Discord Developer Portal](https://discord.com/developers/applications)
   - Create a new application
   - Navigate to the "Bot" section
   - Click "Add Bot" and copy the bot token

2. **Bot Permissions:**
   - Enable "Send Messages" permission
   - Enable "Read Message History" (if needed)

3. **Invite Bot to Server:**
   - Go to OAuth2 → URL Generator
   - Select `bot` scope and required permissions
   - Use the generated URL to invite the bot to your server

4. **Get Discord Channel IDs:**
   - Enable Developer Mode in Discord (User Settings → Advanced)
   - Right-click on any channel and select "Copy ID"

## Configuration

### Environment Variables

```bash
export DISCORD_BOT_TOKEN="your-bot-token-here"
# Optional if you want the bootstrap script to inline credentials
export LINEAR_CLIENT_ID="client-id"
export LINEAR_CLIENT_SECRET="client-secret"
```

### Server Configuration

After running `pnpm bootstrap:linear`, `.mcp-workflows/servers.json` contains:

- **Linear**: `npx mcp-remote https://mcp.linear.app/mcp` (OAuth, token cache at `.tokens/linear`)
- **Discord**: `npx @chinchillaenterprises/mcp-discord` stdio server

Inspect the Linear catalog:

```bash
npx mcporter list linear --config examples/linear-discord-demo/.mcp-workflows
```

## Workflows

### Auto-Discovery Workflows

These workflows expose all tools from Linear and Discord:

- `linear_helper`: Access all Linear MCP tools
- `discord_helper`: Access all Discord MCP tools  
- `linear_discord_helper`: Access both Linear and Discord tools

**Usage:**

1. Discover tools:
   ```json
   describe_tools({ "workflow": "linear_helper" })
   ```

2. Call a tool:
   ```json
   discord_helper({
     "tool": "send_message",
     "args": {
       "channelId": "123456789012345678",
       "content": "Hello from Linear!"
     }
   })
   ```

## Usage

1. **Set environment variables:**
   ```bash
   export DISCORD_BOT_TOKEN="your-bot-token"
   ```

2. **Start Oplink:**
   ```bash
   pnpm -r --filter ./packages/oplink dev -- --config examples/linear-discord-demo/.mcp-workflows
   ```

3. **In your MCP client:**
   - Connect to the running Oplink server
   - List tools to see available workflows
   - Call workflows with appropriate parameters

## Example Workflow

1. **Discover available tools:**
   ```json
   describe_tools({ "workflow": "linear_helper" })
   describe_tools({ "workflow": "discord_helper" })
   ```

2. **Send a notification to a channel:**
   ```json
   discord_helper({
     "tool": "send_message",
     "args": {
       "channelId": "123456789012345678",
       "content": "📋 New Linear issue: Fix authentication bug"
     }
   })
   ```

## Troubleshooting

- **"Missing MCP server registry"**: Ensure `servers.json` exists in `.mcp-workflows/`
- **"Environment placeholder cannot be resolved"**: Export `DISCORD_BOT_TOKEN`
- **Linear OAuth errors**: Delete `.mcp-workflows/.tokens/linear` and re-run `pnpm bootstrap:linear`
- **Discord errors**: Ensure the bot token is valid and the bot has necessary permissions
- **Tool not found**: Use `describe_tools` to verify the exact tool names exposed by each server



## Links discovered
- [Discord Developer Portal](https://discord.com/developers/applications)

--- docs/oplink-docs/content/4.examples/2-deepwiki.md ---
---
title: DeepWiki Integration
description: Integrate the public DeepWiki MCP server for documentation search
---

This example integrates the public DeepWiki MCP server (documented at [https://docs.devin.ai/work-with-devin/deepwiki-mcp](https://docs.devin.ai/work-with-devin/deepwiki-mcp)) into Oplink.

## Configuration

The example includes:

- `.mcp-workflows/servers.json` – registers the HTTP endpoint `https://mcp.deepwiki.com/sse` under the alias `deepwiki`. No API key is required per the official docs.
- `.mcp-workflows/workflows.yaml` – defines a scripted workflow (`deepwiki_lookup`) that calls DeepWiki behind the scenes.

## Workflows

### `deepwiki_lookup`

Scripted workflow that calls DeepWiki’s `ask_question` tool:

```yaml
deepwiki_lookup:
  description: "Ask DeepWiki a question about a GitHub repository"
  runtime: scripted
  parameters:
    repo:
      type: "string"
      description: "owner/repo, e.g. facebook/react"
      required: true
    question:
      type: "string"
      description: "Question to ask about the repository"
      required: true
  steps:
    - call: deepwiki:ask_question
      args:
        repoName: "{{ repo }}"
        question: "{{ question }}"
```

## Usage

1. **Start Oplink:**
   ```bash
   pnpm -r --filter ./packages/oplink dev -- --config examples/deepwiki-demo/.mcp-workflows
   ```

2. **Inspect the server (optional):**
   ```bash
   npx mcporter list deepwiki --config examples/deepwiki-demo/.mcp-workflows
   ```

3. **Discover cached tools:**
   ```json
   describe_tools({ "workflow": "deepwiki_lookup" })
   ```

4. **Call the workflow:**
   ```json
   deepwiki_lookup({
     "repo": "shadcn-ui/ui",
     "question": "How do I use the dialog component?"
   })
   ```

By default, `listTools` will show `deepwiki_lookup` plus helper tools such as `describe_tools` and `external_auth_setup`. If you prefer to expose one MCP tool per DeepWiki tool (e.g., `deepwiki.read_wiki_structure`), start Oplink with:

```bash
OPLINK_AUTO_REGISTER_EXTERNAL_TOOLS=1 pnpm -r --filter ./packages/oplink dev -- --config examples/deepwiki-demo/.mcp-workflows
```


## Links discovered
- [https://docs.devin.ai/work-with-devin/deepwiki-mcp](https://docs.devin.ai/work-with-devin/deepwiki-mcp)

--- docs/oplink-docs/content/4.examples/3-context7.md ---
---
title: Context7 Integration
description: Integrate Upstash Context7 for library documentation lookup
---

This example shows how to wire Upstash Context7 into Oplink via the MCP registry.

## Prerequisites

Get an API key from [https://context7.upstash.io](https://context7.upstash.io).

## Configuration

### Environment Variables

```bash
export CONTEXT7_API_KEY="sk-..."
```

### Server Configuration

- `.mcp-workflows/servers.json` – declares the `context7` alias using the published `@upstash/context7-mcp` stdio command. The `${CONTEXT7_API_KEY}` placeholder is expanded at startup.

- `.mcp-workflows/workflows.yaml` – defines two workflows:
  - `context7_demo`: prompt workflow that exposes Context7 tools via `externalServers`
  - `context7_auto`: auto-discovery workflow that exposes all Context7 tools

## Workflows

### `context7_demo`

Prompt workflow that exposes Context7 tools:

```yaml
context7_demo:
  description: "Answer documentation questions via Context7"
  parameters:
    topic:
      type: "string"
      description: "Library or topic to research"
      required: true
  prompt: |
    Use context7:get-library-docs with the topic {{ topic }}.
    Summarize the most relevant APIs and callouts for the user.
  externalServers:
    - context7
```

### `context7_auto`

Auto-discovery workflow:

```yaml
context7_auto:
  description: "Auto-discovered Context7 tools"
  prompt: |
    Use Context7 tools as needed to collect documentation and answer the request.
  externalServers:
    - context7
```

## Usage

1. **Export the API key:**
   ```bash
   export CONTEXT7_API_KEY="sk-..."
   ```

2. **Start Oplink:**
   ```bash
   pnpm -r --filter ./packages/oplink dev -- --config examples/context7-demo/.mcp-workflows
   ```

3. **In your MCP client:**
   - Connect to the running Oplink server
   - List tools to see available workflows
   - Invoke either `context7_demo` or the proxy tool `context7:get-library-docs` directly

## Example

```json
context7_demo({
  "topic": "react hooks"
})
```

If `CONTEXT7_API_KEY` is missing or invalid, Oplink will fail fast during startup, ensuring there are no silent fallbacks.



## Links discovered
- [https://context7.upstash.io](https://context7.upstash.io)

--- docs/oplink-docs/content/4.examples/4-frontend-mcp.md ---
---
title: Frontend MCP Demo
description: Integrate Chrome DevTools and shadcn MCP servers for frontend debugging and component discovery
---

This example demonstrates how to integrate Chrome DevTools MCP and shadcn MCP servers for frontend development workflows. The demo includes a sample React application that you can debug and inspect using MCP tools.

## Overview

The frontend-mcp-demo combines:

- **Chrome DevTools MCP**: Live browser debugging, screenshots, and performance analysis
- **shadcn MCP**: Browse, search, and install UI components from the shadcn/ui library

## Prerequisites

1. **Chrome/Chromium browser** installed (required for Chrome DevTools MCP)
2. **Node.js** and `pnpm` installed
3. **FRONTEND_ROOT environment variable** (optional, for shadcn MCP):
   ```bash
   export FRONTEND_ROOT="/path/to/your/frontend/project"
   ```

## Configuration

### Server Configuration

The `.mcp-workflows/servers.json` file registers two MCP servers:

- **chrome-devtools**: Uses `npx chrome-devtools-mcp@latest` for browser debugging
- **shadcn**: Uses `npx shadcn@latest mcp` for component discovery (requires `FRONTEND_ROOT`)

### Workflows

#### `frontend_debugger`

Auto-discovery workflow that exposes all Chrome DevTools and shadcn tools:

```yaml
frontend_debugger:
  description: "Direct access to Chrome DevTools + shadcn MCP tools"
  prompt: |
    You are a frontend debugger. Use Chrome DevTools MCP or shadcn MCP tools
    to inspect the UI, capture screenshots, or list components.
  externalServers:
    - chrome-devtools
    - shadcn
```

**Usage:**

1. Discover available tools:
   ```json
   describe_tools({ "workflow": "frontend_debugger" })
   ```

2. Take a screenshot:
   ```json
   frontend_debugger({
     "tool": "chrome-devtools:take_screenshot",
     "args": {
       "url": "http://localhost:5173",
       "format": "png"
     }
   })
   ```

#### `ui_components_helper`

Dedicated helper for shadcn component discovery:

```yaml
ui_components_helper:
  description: "Dedicated shadcn MCP helper"
  prompt: |
    Use shadcn MCP tools to list or search components.
  externalServers:
    - shadcn
```

**Usage:**

```json
ui_components_helper({
  "tool": "shadcn:search_items_in_registries",
  "args": {
    "query": "button"
  }
})
```

#### `take_screenshot`

Scripted workflow for capturing page screenshots:

```yaml
take_screenshot:
  description: "Capture screenshots of web pages"
  runtime: scripted
  parameters:
    url:
      type: "string"
      required: true
    format:
      type: "string"
      enum: ["png", "jpeg", "webp"]
      default: "png"
  steps:
    - call: chrome-devtools:navigate_page
      args:
        url: "{{ url }}"
    - call: chrome-devtools:take_screenshot
      args:
        format: "{{ format }}"
```

**Usage:**

```json
take_screenshot({
  "url": "http://localhost:5173",
  "format": "png"
})
```

## Usage

1. **Start the demo frontend application:**
   ```bash
   cd examples/frontend-mcp-demo
   pnpm install
   pnpm dev
   ```
   The app will be available at `http://localhost:5173`

2. **Set environment variables (if using shadcn MCP):**
   ```bash
   export FRONTEND_ROOT="/path/to/your/frontend/project"
   ```

3. **Start Oplink:**
   ```bash
   pnpm -r --filter ./packages/oplink dev -- --config examples/frontend-mcp-demo/.mcp-workflows
   ```

4. **In your MCP client:**
   - Connect to the running Oplink server
   - Use `describe_tools` to discover available Chrome DevTools and shadcn tools
   - Capture screenshots, inspect console logs, or search for UI components

## Example Workflows

### Debugging a Web Page

1. **Navigate and capture a screenshot:**
   ```json
   take_screenshot({
     "url": "http://localhost:5173",
     "format": "png",
     "wait_for": "Frontend MCP demo"
   })
   ```

2. **Inspect console logs:**
   ```json
   frontend_debugger({
     "tool": "chrome-devtools:get_console_logs",
     "args": {}
   })
   ```

3. **Check network requests:**
   ```json
   frontend_debugger({
     "tool": "chrome-devtools:list_network_requests",
     "args": {}
   })
   ```

### Finding UI Components

1. **Search for components:**
   ```json
   ui_components_helper({
     "tool": "shadcn:search_items_in_registries",
     "args": {
       "query": "dialog"
     }
   })
   ```

2. **List available components:**
   ```json
   ui_components_helper({
     "tool": "shadcn:list_items_in_registries",
     "args": {}
   })
   ```

## Troubleshooting

- **Chrome DevTools errors**: Ensure Chrome/Chromium is installed and accessible
- **shadcn MCP errors**: Verify `FRONTEND_ROOT` is set to a valid project directory
- **Screenshot failures**: Make sure the target URL is accessible and the page has loaded
- **Tool not found**: Use `describe_tools` to verify the exact tool names exposed by each server



--- examples/atlassian-demo/README.md ---
# Atlassian (Jira + Confluence) Demo Config

This example demonstrates how to integrate Atlassian Jira and Confluence MCP servers into Oplink workflows. The demo includes workflows for common Jira and Confluence operations.

## Key Features

- **Auto-discovery workflows**: Access all Jira and Confluence tools dynamically
- **Scripted workflows**: Pre-built workflows for common operations
- **Issue management**: Create, update, and search Jira issues
- **Documentation access**: Search and retrieve Confluence pages

## Prerequisites

### 1. Docker

The Atlassian MCP server runs as a Docker container. Ensure Docker is installed and running.

### 2. Atlassian API Tokens

#### For Atlassian Cloud (Recommended)

1. Go to https://id.atlassian.com/manage-profile/security/api-tokens
2. Click **Create API token**
3. Name it (e.g., "Oplink MCP")
4. Copy the token immediately (you won't see it again)

You'll need separate tokens for Jira and Confluence if you use different accounts, or one token if using the same account.

#### For Server/Data Center

1. Go to your profile (avatar) → **Profile** → **Personal Access Tokens**
2. Click **Create token**
3. Name it and set expiry
4. Copy the token immediately

## Configuration

### 1. Set Environment Variables

Copy the example environment file and fill in your credentials:

```bash
cp .env.example .env
```

Edit `.env` and add your Atlassian credentials:

```bash
# Jira Configuration
JIRA_URL=https://your-company.atlassian.net
JIRA_USERNAME=your.email@company.com
JIRA_API_TOKEN=your_jira_api_token_here

# Confluence Configuration
CONFLUENCE_URL=https://your-company.atlassian.net/wiki
CONFLUENCE_USERNAME=your.email@company.com
CONFLUENCE_API_TOKEN=your_confluence_api_token_here
```

### 2. Optional: Filter Projects/Spaces

To limit which projects/spaces are accessible, add filters:

```bash
# Only show specific Jira projects
JIRA_PROJECTS_FILTER=PROJ,DEV,SUPPORT

# Only show specific Confluence spaces
CONFLUENCE_SPACES_FILTER=DEV,TEAM,DOC
```

### 3. Export Environment Variables

Before running Oplink, export the environment variables:

```bash
export $(cat .env | xargs)
```

Or use a tool like `direnv` to automatically load them:

```bash
# Install direnv (if not already installed)
brew install direnv  # macOS
# or
sudo apt install direnv  # Linux

# Allow direnv in this directory
echo 'export $(cat .env | xargs)' > .envrc
direnv allow
```

## Workflows

### Auto-Discovery Workflows

These workflows expose all tools from the Atlassian MCP server:

- `atlassian_helper`: Access all Jira and Confluence tools
- `jira_helper`: Access Jira tools only
- `confluence_helper`: Access Confluence tools only

**Usage:**

1. Discover available tools:
   ```json
   describe_tools({ "workflow": "jira_helper" })
   ```

2. Call a tool:
   ```json
   jira_helper({
     "tool": "jira_search",
     "args": {
       "jql": "assignee = currentUser() AND status = 'In Progress'"
     }
   })
   ```

### Scripted Workflows

These workflows provide convenient wrappers for common Jira and Confluence operations:

#### `list_my_issues`

List issues assigned to you with compact output:

```json
list_my_issues({
  "project": "PROJ",
  "status": "In Progress",
  "max_results": 20
})
```


#### `search_issues_compact`

Search issues with JQL and get compact results:

```json
search_issues_compact({
  "jql": "project = PROJ AND created >= -7d ORDER BY updated DESC",
  "max_results": 50
})
```

#### `get_issue_summary`

Get a concise summary of a specific issue:

```json
get_issue_summary({
  "issue_key": "PROJ-123"
})
```

Returns only: key, summary, description (text only), status, priority, assignee, dates, labels, and URL.

#### `create_issue_from_notes`

Create a Jira issue from meeting notes or text:

```json
create_issue_from_notes({
  "project_key": "PROJ",
  "summary": "Fix authentication bug",
  "description": "Users cannot log in after password reset",
  "issue_type": "Bug",
  "priority": "High"
})
```

#### `update_issue_status`

Transition an issue to a new status:

```json
update_issue_status({
  "issue_key": "PROJ-123",
  "status": "In Progress"
})
```

#### `search_confluence_pages`

Search Confluence pages with compact output:

```json
search_confluence_pages({
  "query": "OKR guide",
  "space_key": "TEAM",
  "limit": 20
})
```

#### `get_confluence_page_content`

Get page content with HTML stripped and formatted:

```json
get_confluence_page_content({
  "page_id": "123456789"
})
```

## Usage

1. **Set environment variables:**
   ```bash
   export $(cat .env | xargs)
   ```

2. **Start Oplink:**
   ```bash
   pnpm -r --filter ./packages/oplink dev -- --config examples/atlassian-demo/.mcp-workflows
   ```

3. **In your MCP client (Cursor, Claude, etc.):**
   - Connect to the running Oplink server
   - List tools to see available workflows
   - Call workflows with appropriate parameters

## Example Workflows

### Daily Standup Preparation

1. **List your in-progress issues:**
   ```json
   list_my_issues({
     "status": "In Progress",
     "max_results": 10
   })
   ```

2. **Get details for a specific issue:**
   ```json
   get_issue_summary({
     "issue_key": "PROJ-123"
   })
   ```

### Meeting Notes to Jira

1. **Create issues from meeting notes:**
   ```json
   create_issue_from_notes({
     "project_key": "PROJ",
     "summary": "Implement user authentication",
     "description": "From sprint planning: Need to add OAuth2 support",
     "issue_type": "Story",
     "priority": "High"
   })
   ```

### Documentation Lookup

1. **Search Confluence:**
   ```json
   search_confluence_pages({
     "query": "deployment process",
     "space_key": "DEV"
   })
   ```

2. **Get page content:**
   ```json
   get_confluence_page_content({
     "page_id": "123456789"
   })
   ```


## Troubleshooting

- **"Missing environment variable"**: Ensure all required variables are exported (use `export $(cat .env | xargs)`)
- **"Docker not found"**: Ensure Docker is installed and running
- **"Authentication failed"**: Verify your API tokens are correct and not expired
- **"Permission denied"**: Ensure your Atlassian account has access to the projects/spaces you're trying to access
- **"Tool not found"**: Use `describe_tools` to verify the exact tool names exposed by the server

## Notes

- **API Tokens vs Passwords**: Use API tokens, not your account password
- **Token Security**: Never commit `.env` files to version control
- **Project/Space Filters**: Use `JIRA_PROJECTS_FILTER` and `CONFLUENCE_SPACES_FILTER` to limit access
- **Read-Only Mode**: Set `READ_ONLY_MODE=true` in your environment to disable write operations
- **Tool Filtering**: Use `ENABLED_TOOLS` environment variable to restrict which tools are available



--- examples/context7-demo/README.md ---
# Context7 Demo Config

This folder shows how to wire Upstash Context7 into Oplink via the new MCP registry.

## Files

- `.mcp-workflows/servers.json` – declares the `context7` alias using the published `@upstash/context7-mcp` stdio command. The `${CONTEXT7_API_KEY}` placeholder is expanded at startup, so export that env var before running.
- `.mcp-workflows/workflows.yaml` – defines two workflows:
  - `context7_demo`: uses an explicit proxy tool `context7:get-library-docs`.
  - `context7_auto`: uses `externalServers: [context7]` to auto-register all Context7 tools while still hinting that `context7:get-library-docs` is preferred.

## Usage

1. Grab an API key from [https://context7.upstash.io](https://context7.upstash.io) and export it:
   ```bash
   export CONTEXT7_API_KEY="sk-..."
   ```
2. Start Oplink against this config:
   ```bash
   pnpm -r --filter ./packages/oplink dev -- --config examples/context7-demo/.mcp-workflows
   ```
3. In your MCP client (Cursor, Claude, etc.), connect to the running Oplink server, list tools, and invoke either `context7_demo` or the proxy tool `context7:get-library-docs` directly.

If `CONTEXT7_API_KEY` is missing or invalid, Oplink will fail fast during startup, ensuring there are no silent fallbacks.


## Links discovered
- [https://context7.upstash.io](https://context7.upstash.io)

--- examples/deepwiki-demo/README.md ---
# DeepWiki Demo Config

This example integrates the public DeepWiki MCP server (documented at https://docs.devin.ai/work-with-devin/deepwiki-mcp) into Oplink.

## Files

- `.mcp-workflows/servers.json` – registers the HTTP endpoint `https://mcp.deepwiki.com/sse` under the alias `deepwiki`. No API key is required per the official docs.
- `.mcp-workflows/workflows.yaml` – includes a scripted workflow (`deepwiki_lookup`) that calls DeepWiki's `ask_question` tool behind the scenes.

## Usage

```bash
pnpm -r --filter ./packages/oplink dev -- --config examples/deepwiki-demo/.mcp-workflows
```

After Oplink starts, connect your MCP client and run:

```json
describe_tools({ "workflow": "deepwiki_lookup" })
```

You should see cached schemas for the DeepWiki alias along with the helper tools (`describe_tools`, `external_auth_setup`). Call the scripted workflow directly:

```json
deepwiki_lookup({ "repo": "facebook/react", "question": "What is concurrent mode?" })
```

If you prefer to expose every DeepWiki tool as its own MCP tool (e.g., `deepwiki.read_wiki_structure`), opt in by setting `OPLINK_AUTO_REGISTER_EXTERNAL_TOOLS=1` before starting Oplink. You can also expose the `oplink_info` debugging helper with `OPLINK_INFO_TOOL=1`.

```bash
OPLINK_AUTO_REGISTER_EXTERNAL_TOOLS=1 pnpm -r --filter ./packages/oplink dev -- --config examples/deepwiki-demo/.mcp-workflows
```


--- examples/linear-discord-demo/README.md ---
# Linear + Discord Demo Config

This example demonstrates how to integrate Linear (issue management) and Discord (messaging) MCP servers into Oplink workflows. You can fetch Linear issues and send Discord notifications to assignees.

## Prerequisites

### Linear Setup

Linear’s hosted MCP server uses OAuth 2.1. mcporter handles the browser/device flow, so you simply need to generate the `servers.json` entry once:

1. Run the bootstrap helper (it defaults to this example directory):
   ```bash
   pnpm bootstrap:linear
   ```
   - The script copies `servers.json.example` → `servers.json`, ensures `.tokens/linear` exists, and optionally stores the client ID/secret you paste.
   - Skip the prompts to rely on Linear’s dynamic registration—mcporter will open a browser the first time a workflow calls a Linear tool.

2. (Optional) If you want a reusable OAuth client, create one under **Linear → Settings → API** and paste the client ID/secret when the script asks.

3. For scripted workflows you may still want team IDs or issue IDs; grab them from the Linear UI as before.

### Discord Setup

1. **Create a Discord Bot:**
   - Go to [Discord Developer Portal](https://discord.com/developers/applications)
   - Create a new application
   - Navigate to the "Bot" section
   - Click "Add Bot" and copy the bot token

2. **Bot Permissions:**
   - Enable "Send Messages" permission
   - Enable "Read Message History" (if needed)
   - For direct messages, ensure the bot can access the user

3. **Invite Bot to Server:**
   - Go to OAuth2 → URL Generator
   - Select `bot` scope and required permissions
   - Use the generated URL to invite the bot to your server

4. **Get Discord Channel IDs:**
   - Enable Developer Mode in Discord (User Settings → Advanced)
   - Right-click on any channel (e.g., #general) and select "Copy ID"
   - You'll need the channel ID to send messages to that channel

5. **Get Discord User IDs (for DMs):**
   - With Developer Mode enabled, right-click on users to copy their User ID
   - You'll need these to send direct messages

## Configuration

### Environment Variables

Only Discord requires an environment variable for this demo. Linear auth is handled via the OAuth prompts/token cache.

```bash
cp .env.example .env   # optional helper; stores placeholders only
```

```bash
export DISCORD_BOT_TOKEN="your-bot-token-here"
# Optional if you want the bootstrap script to inline credentials
export LINEAR_CLIENT_ID="client-id"
export LINEAR_CLIENT_SECRET="client-secret"
```

### Server Configuration

After running `pnpm bootstrap:linear`, `.mcp-workflows/servers.json` contains:

- **Linear**: `npx mcp-remote https://mcp.linear.app/mcp` (`auth: "oauth"`, token cache at `.tokens/linear`).
- **Discord**: `npx @chinchillaenterprises/mcp-discord` stdio server.

You can inspect the Linear catalog at any time with:

```bash
npx mcporter list linear --config examples/linear-discord-demo/.mcp-workflows
```

Tokens generated during OAuth live under `.mcp-workflows/.tokens/linear` and are ignored by git.

## Workflows

### Auto-Discovery Workflows

These workflows expose all tools from Linear and Discord for flexible usage:

- `linear_helper`: Access all Linear MCP tools
- `discord_helper`: Access all Discord MCP tools  
- `linear_discord_helper`: Access both Linear and Discord tools

**Usage:**
1. Call `describe_tools({ "workflow": "linear_helper" })` to discover available tools
2. Call the workflow with `{"tool": "tool_name", "args": {...}}`

### Scripted Workflows

Pre-built workflows for common tasks:

#### `get_team_issues`
Fetch issues from a Linear team.

```json
{
  "team_id": "team-id-here",
  "state": "In Progress"
}
```

#### `send_discord_notification`
Send a message to a Discord channel or user.

**To send to a channel (e.g., #general):**
```json
{
  "channel_id": "123456789012345678",
  "message": "Hello from Linear!"
}
```

**To send a direct message to a user:**
```json
{
  "channel_id": "987654321098765432",
  "message": "You have a new Linear issue assigned!"
}
```

**Note:** Use the channel ID for channels and the user ID for direct messages. Both use the same `channel_id` parameter in the Discord API.

#### `notify_assignee`
Get a Linear issue and send a Discord notification to the assignee.

```json
{
  "issue_id": "abc-123",
  "discord_user_id": "987654321098765432"
}
```

## Usage

1. **Set environment variables:**
   ```bash
   cp .env.example .env  # optional helper for local shells
   export DISCORD_BOT_TOKEN="your-bot-token"
   # Optional, only needed if you want the bootstrap script to auto-fill credentials
   export LINEAR_CLIENT_ID="client-id"
   export LINEAR_CLIENT_SECRET="client-secret"
   ```

2. **Start Oplink:**
   ```bash
   pnpm -r --filter ./packages/oplink dev -- --config examples/linear-discord-demo/.mcp-workflows
   ```

3. **In your MCP client (Cursor, Claude, etc.):**
   - Connect to the running Oplink server
   - List tools to see available workflows
   - Call workflows with appropriate parameters

## Example Workflow

Here's a typical workflow to notify assignees about Linear issues:

1. **Discover available tools:**
   ```json
   describe_tools({ "workflow": "linear_helper" })
   describe_tools({ "workflow": "discord_helper" })
   ```

2. **Get issues for a team:**
   ```json
   get_team_issues({
     "team_id": "your-team-id",
     "state": "In Progress"
   })
   ```

3. **Send a notification to a channel (e.g., #general):**
   ```json
   send_discord_notification({
     "channel_id": "123456789012345678",
     "message": "New Linear issues are ready for review!"
   })
   ```

4. **Or send a direct message to an assignee:**
   ```json
   notify_assignee({
     "issue_id": "abc-123",
     "discord_user_id": "987654321098765432"
   })
   ```

## Sending Messages to Channels

To send messages to a Discord channel (like #general):

1. **Get the Channel ID:**
   - Enable Developer Mode in Discord (User Settings → Advanced)
   - Right-click on the channel name (e.g., #general)
   - Select "Copy ID"
   - The ID is a long number like `123456789012345678`

2. **Use the Channel ID:**
   ```json
   send_discord_notification({
     "channel_id": "123456789012345678",
     "message": "📋 New Linear issue: Fix authentication bug"
   })
   ```

3. **Or use the auto-discovery workflow:**
   ```json
   discord_helper({
     "tool": "send_message",
     "args": {
       "channelId": "123456789012345678",
       "content": "Hello from Linear!"
     }
   })
   ```

**Important:** Make sure your bot has been invited to the server and has "Send Messages" permission in the channel you want to post to.

## Notes

- **Tool Names**: The actual tool names (`list_issues`, `get_issue`, `send_message`, etc.) depend on what the Linear and Discord MCP servers expose. Use `describe_tools` to discover the exact names and schemas.

- **Linear MCP Endpoint**: The demo defaults to `https://mcp.linear.app/mcp` via `npx mcp-remote`. If Linear rotates endpoints, edit `servers.json` or re-run `pnpm bootstrap:linear`.

- **Discord Direct Messages**: Bots can send DMs, but users must have DMs enabled from server members. Consider using a channel instead if DMs fail.

- **User Mapping**: You'll need to maintain a mapping between Linear user emails/IDs and Discord user IDs to send targeted notifications.

## Troubleshooting

- **"Missing MCP server registry"**: Ensure `servers.json` exists in `.mcp-workflows/`
- **"Environment placeholder cannot be resolved"**: Export `DISCORD_BOT_TOKEN` (and optionally `LINEAR_CLIENT_ID` / `LINEAR_CLIENT_SECRET` if you want the script to inline them)
- **Linear OAuth errors**: Delete `.mcp-workflows/.tokens/linear` and re-run `pnpm bootstrap:linear` to retry the login flow
- **Discord errors**: Ensure the bot token is valid and the bot has necessary permissions. The demo uses `@chinchillaenterprises/mcp-discord`, which reads `DISCORD_TOKEN` from the environment (wired here from `DISCORD_BOT_TOKEN`).
- **Tool not found**: Use `describe_tools` to verify the exact tool names exposed by each server


## Links discovered
- [Discord Developer Portal](https://discord.com/developers/applications)

--- examples/universal-proxy-demo/README.md ---
# Universal Proxy Demo (Auto‑Discovery, Multiple Servers)

This example shows how a single Oplink workflow can proxy multiple external MCP servers and discover their tools automatically via mcporter. You can call any tool exposed by the servers you declare in `.mcp-workflows/servers.json` — Oplink + mcporter will launch/connect and run it for you.

## What’s included

- Two external servers, different transports:
  - `chrome-devtools` (stdio) — launched via `npx chrome-devtools-mcp@latest`
  - `deepwiki` (HTTP SSE) — public endpoint, no auth
- One auto‑discovery workflow `universal_helper` exposing all tools from both servers
- One scripted example `screenshot_and_lookup` composing tools across servers

## Prerequisites

- Node.js and `pnpm`
- Chrome/Chromium installed (for Chrome DevTools MCP)

## Run Oplink MCP server

Using the repo workspace build:

```bash
pnpm -r --filter ./packages/oplink dev -- --config examples/universal-proxy-demo/.mcp-workflows
```

Or using the published CLI (if installed globally/locally):

```bash
npx oplink server --config examples/universal-proxy-demo/.mcp-workflows
```

Then connect your MCP‑compatible client (Cursor, Claude Desktop, etc.) to the running Oplink stdio server.

## Discover tools

Ask the built‑in helper to list tools available to this workflow:

```json
describe_tools({ "workflow": "universal_helper" })
```

## Call any tool from any server

Call by fully‑qualified name `alias:tool` or provide `server` separately:

```json
universal_helper({
  "tool": "chrome-devtools:take_screenshot",
  "args": { "url": "https://example.com", "format": "png" }
})
```

```json
universal_helper({
  "server": "deepwiki",
  "tool": "ask_question",
  "args": { "repoName": "facebook/react", "question": "What is the new JSX transform?" }
})
```

## Scripted composition example

```json
screenshot_and_lookup({
  "url": "https://example.com",
  "repo": "facebook/react",
  "question": "How does Suspense scheduling work?"
})
```

## How it works

Declare servers in `.mcp-workflows/servers.json`. Oplink hands off launch/transport, discovery, and schema validation to mcporter. When you invoke a tool, Oplink forwards it to mcporter, which connects (stdio or HTTP) and executes the tool. No duplication of tool schemas in your repo is required.


--- examples/frontend-mcp-demo/index.html ---
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Oplink Frontend MCP Demo</title>
  </head>
  <body class="bg-background text-foreground">
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


--- HOW-TO-RELEASE.md ---
# How To Release oplink

This project ships via the Node script at `scripts/release.ts`. The script bumps versions across all packages in the monorepo, creates git commits and tags, and publishes packages to npm.

## Prerequisites
- Node 18+ (or Node 20+)
- pnpm (`corepack enable && corepack prepare pnpm@latest --activate` works too)
- npm auth: `npm whoami` works; 2FA ready if enabled
- Clean `main` branch with all changes committed and pushed to origin

## Project Structure
This is a monorepo with multiple packages:
- **Root** (`@instructa/oplink`): Private, version bumped but not published
- **@oplink/core** (`packages/oplink`): Published as public
- **oplink** (`packages/cli`): Published as public (CLI package)
- **@oplink/test-utils** (`packages/test-utils`): Version bumped but not published

## Prepare
- Ensure all changes are committed and pushed to `main`
- Run lint and tests to verify everything passes:
  - `pnpm lint`
  - `pnpm test:ci` (runs build + tests non-interactively)
- Update any user-facing documentation if needed

## Quick Release (with Preflight)
- Patch/minor/major bump and publish:
  - `pnpm dlx tsx scripts/release.ts patch` (or `minor`/`major`)
  - Or use a specific version: `pnpm dlx tsx scripts/release.ts 0.1.0`
- The script runs a preflight before any version bump/tag/publish:
  1. Verify a clean working tree (fails if uncommitted changes exist)
  2. Dependency guard for publishable packages (`oplink`, `@oplink/core`):
     - Rejects runtime specifiers `workspace:` and `catalog:` in `dependencies`, `peerDependencies`, and `optionalDependencies`
  3. Build packages (Rolldown) and run tests (Vitest, non‑interactive)
  4. Artifact checks:
     - CLI must include: `bin/oplink.mjs`, `dist/schema/oplink-workflows.schema.json`, `dist/schema/oplink-servers.schema.json`
     - Core build output present in `dist/`
  5. `npm pack --dry-run` must succeed for each publishable package
  6. Sanity: `npx -y oplink@file:<path-to-tgz> --help` works (optional)
  
  If any preflight step fails, the script aborts before changing versions or creating tags.

- On success, the script then:
  - Bumps versions in root and all packages
  - Creates commit `chore: release vX.Y.Z`
  - Creates tag `vX.Y.Z`
  - Pushes commit and tags
  - Publishes `@oplink/core` and `oplink` to npm with `--access public`
  - Skips `@oplink/test-utils` (not configured for publishing)

### Preflight Only (no version bump)
- Run just the checks without publishing:
  - `pnpm dlx tsx scripts/release.ts preflight`
  - Exits non‑zero on failure; prints the exact blocking step.

## Full Release Checklist

Before you start
- `git status` is clean on `main` and pushed.
- `npm whoami` succeeds; 2FA ready if enabled.
- Node 18+ or 20+; pnpm installed.

Run preflight (required)
- `pnpm dlx tsx scripts/release.ts preflight`
  - Blocks on:
    - runtime deps using `workspace:`/`catalog:` specifiers
    - missing CLI/core artifacts (schemas/presets/bin)
    - failing `npm pack --dry-run`

Publish
- Choose bump: `patch` | `minor` | `major` | `<X.Y.Z>`
  - `pnpm dlx tsx scripts/release.ts patch`

Post‑publish verification
- `npm view oplink version` and `npm view @oplink/core version`
- Quick smoke:
  - `npx -y oplink@latest --help`
  - `npx -y oplink@latest validate --config examples/deepwiki-demo/.mcp-workflows`
  - Fresh project smoke (recommended):
    ```bash
    mkdir -p ~/projects/oplink-test && cd ~/projects/oplink-test
    pnpm init -y && pnpm add -D oplink@latest
    mkdir -p .mcp-workflows && printf "hello:\n  description: Hello\n  prompt: |\n    # Hello World\n" > .mcp-workflows/hello.yml
    npx oplink validate --config ./.mcp-workflows
    npx oplink server --config ./.mcp-workflows # start once to ensure runtime ok
    ```
- Push tag created by script is visible: `git tag -l v*`

Deprecate broken versions (if needed)
- `npm deprecate oplink@<bad> "Deprecated. Please use oplink@<good> or later."`
- `npm deprecate @oplink/core@<bad> "Deprecated. Please use @oplink/core@<good> or later."`

## Sanity Checks (optional but recommended)
- Build locally before releasing:
  - `pnpm build` (builds all packages)
- Verify packages after publish:
  - Check npm pages: `https://www.npmjs.com/package/oplink` and `https://www.npmjs.com/package/@oplink/core`
  - Test installation: `npx oplink@latest --version`
  - Verify git tag exists: `git tag -l v*`

## Deprecation Policy
- If an early version has known issues (e.g., missing files or unusable/broken workflows), deprecate it to guide users to a working version:
  - `npm deprecate oplink@<bad> "Deprecated. Please use oplink@<good> or later."`
  - `npm deprecate @oplink/core@<bad> "Deprecated. Please use @oplink/core@<good> or later."`
- Prefer deprecating over unpublishing. Only unpublish if absolutely necessary and within the npm time window.

## Troubleshooting Preflight
- Error about `workspace:`/`catalog:` in deps:
  - Replace those specifiers with real semver ranges for publishable packages.
- Missing artifact error:
  - Ensure build outputs land in `dist/` (schemas/bin). Update build configs if needed.
- `npm pack --dry-run` fails:
  - Inspect the error, then re-run the release after fixing.
 - Tests fail locally but pass in CI:
   - `pnpm store prune && rm -rf node_modules && pnpm install --force`
   - Re-run: `pnpm -r build && pnpm test:ci`

## GitHub Releases
The release script does not automatically create GitHub Releases. After publishing:
- Manually create a GitHub Release from the tag if needed
- Include release notes describing changes in the new version

## Prereleases / Dist-Tags
The current script doesn't support dist-tags. To publish a prerelease:
1. Run the release script normally to bump versions and create tags
2. Manually publish with a tag:
   - `pnpm -C packages/oplink publish --no-git-checks --tag alpha`
   - `pnpm -C packages/cli publish --no-git-checks --tag alpha`

## Rollback / Deprecation
- Prefer deprecation over unpublish:
  - `npm deprecate oplink@X.Y.Z "Reason…"`
  - `npm deprecate @oplink/core@X.Y.Z "Reason…"`
- Only unpublish if necessary and allowed (within 72 hours):
  - `npm unpublish oplink@X.Y.Z --force`
  - `npm unpublish @oplink/core@X.Y.Z --force`
- Create a follow-up patch release that fixes the issue

## Troubleshooting
- `npm ERR! code E403` or auth failures: run `npm login` and retry
- Working tree not clean: commit or stash changes before running release script
- Tag push rejected: pull/rebase or fast-forward `main`, then rerun
- Package build failures: ensure `pnpm build` succeeds before running release script


--- examples/frontend-mcp-demo/tailwind.config.ts ---
import type { Config } from "tailwindcss"
import plugin from "tailwindcss/plugin"

const config = {
  darkMode: ["class"],
  content: ["./index.html", "./src/**/*.{ts,tsx}"],
  theme: {
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      keyframes: {
        "accordion-down": {
          from: { height: 0 },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: 0 },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
  plugins: [
    plugin(({ addBase }) => {
      addBase({
        ":root": {
          fontFamily: "'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
        },
      })
    }),
  ],
} satisfies Config

export default config


--- examples/frontend-mcp-demo/vite.config.ts ---
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import path from "node:path"

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
    },
  },
  server: {
    port: 3000,
  },
})


--- packages/cli/test/tool-integration.examples.test.ts ---
import { describe, it, beforeAll, afterAll, expect } from "vitest";
import { McpTestClient } from "@oplink/test-utils";
import * as fs from "fs";
import * as path from "path";
import { getModulePaths, ensureDirAndWriteYamlFile, createTestClient } from "./utils";

const { __dirname } = getModulePaths(import.meta.url);
const WF_DIR = path.join(__dirname, ".mcp-workflows-examples", ".mcp-workflows");

let client: McpTestClient;

beforeAll(async () => {
  if (!fs.existsSync(WF_DIR)) fs.mkdirSync(WF_DIR, { recursive: true });
  const examplesYaml = {
    test_calculator: {
      name: "calculator",
      description: "Perform mathematical calculations",
      parameters: {
        expression: { type: "string", description: "The mathematical expression to evaluate", required: true },
        precision: { type: "number", description: "Number of decimal places in the result", default: 2 },
      },
      prompt: "Evaluate the expression with the given precision.",
    },
  };
  ensureDirAndWriteYamlFile(path.join(WF_DIR, "workflows.yaml"), examplesYaml);

  client = createTestClient();
  await client.connectServer(["--config", WF_DIR]);
}, 15000);

afterAll(async () => { if (client) await client.close(); }, 15000);

describe("Parameterized Tool Integration Tests", () => {
  it("should list the parameterized tool", async () => {
    const tools = await client.listTools();
    console.log("Tools response structure:", JSON.stringify(tools, null, 2));

    expect(tools).toHaveProperty("tools");
    expect(Array.isArray(tools.tools)).toBe(true);

    const calculatorTool = tools.tools.find((tool: any) => tool.name === "calculator");
    expect(calculatorTool).toBeTruthy();
    expect(calculatorTool).toHaveProperty("description");
    expect(calculatorTool.description).toContain("calculation");
  }, 15000);

  it("should call the parameterized tool with arguments", async () => {
    const result = await client.callTool("calculator", { expression: "2 + 2", precision: 0 });
    console.log("Tool call result:", JSON.stringify(result, null, 2));

    expect(result).toHaveProperty("content");
    expect(Array.isArray(result.content)).toBe(true);
    expect(result.content[0]).toHaveProperty("type", "text");
    // integration tests depend on the tool actually succeeding now, not surfacing internal Zod errors
    expect(result.isError).toBeUndefined();
  }, 15000);
});


--- scripts/release.ts ---
import { execSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";

interface PackageTarget {
	name: string;
	dir: string;
	bump?: boolean;
	publish?: boolean;
	access?: "public" | "restricted";
}

const packageTargets: PackageTarget[] = [
	{ name: "root", dir: ".", bump: true },
	{ name: "@oplink/core", dir: "packages/oplink", bump: true, publish: true, access: "public" },
	{ name: "oplink", dir: "packages/cli", bump: true, publish: true, access: "public" },
	{ name: "@oplink/test-utils", dir: "packages/test-utils", bump: true },
];

function run(command: string, cwd: string) {
	console.log(`Executing: ${command} in ${cwd}`);
	execSync(command, { stdio: "inherit", cwd });
}

function ensureCleanWorkingTree() {
	const status = execSync("git status --porcelain", { cwd: "." })
		.toString()
		.trim();
	if (status.length > 0) {
		throw new Error(
			"Working tree has uncommitted changes. Please commit or stash them before running the release script.",
		);
	}
}

/**
 * Bump version in package.json
 * @param pkgPath Path to the package directory
 * @param type Version bump type: 'major', 'minor', 'patch', or specific version
 * @returns The new version
 */
function bumpVersion(
	pkgPath: string,
	type: "major" | "minor" | "patch" | string,
): string {
	const pkgJsonPath = path.join(pkgPath, "package.json");
	const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
	const currentVersion = pkgJson.version;
	let newVersion: string;

	if (type === "major" || type === "minor" || type === "patch") {
		// Parse current version
		const [major, minor, patch] = currentVersion.split(".").map(Number);

		// Bump version according to type
		if (type === "major") {
			newVersion = `${major + 1}.0.0`;
		} else if (type === "minor") {
			newVersion = `${major}.${minor + 1}.0`;
		} else {
			// patch
			newVersion = `${major}.${minor}.${patch + 1}`;
		}
	} else {
		// Use the provided version string directly
		newVersion = type;
	}

	// Update package.json
	pkgJson.version = newVersion;
	fs.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`);

	console.log(
		`Bumped version from ${currentVersion} to ${newVersion} in ${pkgJsonPath}`,
	);
	return newVersion;
}

/**
 * Bump version in all package.json files
 * @param versionBump Version bump type or specific version
 * @returns The new version
 */
function bumpAllVersions(
	versionBump: "major" | "minor" | "patch" | string = "patch",
): string {
	const rootTarget = packageTargets.find(
		(target) => target.dir === "." && target.bump,
	);
	if (!rootTarget) {
		throw new Error("Release script requires a root package entry");
	}
	const rootPath = path.resolve(rootTarget.dir);
	const newVersion = bumpVersion(rootPath, versionBump);

	for (const target of packageTargets) {
		if (!target.bump || target.dir === ".") {
			continue;
		}
		const pkgPath = path.resolve(target.dir);
		const manifestPath = path.join(pkgPath, "package.json");
		if (!fs.existsSync(manifestPath)) {
			console.warn(`Skipping ${target.name}; no package.json found at ${manifestPath}`);
			continue;
		}
		bumpVersion(pkgPath, newVersion);
	}

	return newVersion;
}

/**
 * Create a git commit and tag for the release
 * @param version The version to tag
 */
function createGitCommitAndTag(version: string) {
	console.log("Creating git commit and tag...");

	try {
		// Stage all changes
		run("git add .", ".");

		// Create commit with version message
		run(`git commit -m "chore: release v${version}"`, ".");

		// Create tag
		run(`git tag -a v${version} -m "Release v${version}"`, ".");

		// Push commit and tag to remote
		console.log("Pushing commit and tag to remote...");
		run("git push", ".");
		run("git push --tags", ".");

		console.log(`Successfully created and pushed git tag v${version}`);
	} catch (error) {
		console.error("Failed to create git commit and tag:", error);
		throw error;
	}
}

function rewriteCliCoreDependency(newVersion: string) {
    // Ensure CLI depends on the just-bumped @oplink/core
    const cliPath = path.resolve("packages/cli");
    const manifestPath = path.join(cliPath, "package.json");
    if (!fs.existsSync(manifestPath)) return;
    const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
    if (manifest?.dependencies?.["@oplink/core"]) {
        manifest.dependencies["@oplink/core"] = `^${newVersion}`;
        fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
        console.log(`Rewrote CLI dependency @oplink/core -> ^${newVersion}`);
    }
}

async function publishPackages(
    versionBump: "major" | "minor" | "patch" | string = "patch",
) {
    ensureCleanWorkingTree();

    // 1) Bump versions first so we can rewrite internal deps to proper ranges
    const newVersion = bumpAllVersions(versionBump);
    rewriteCliCoreDependency(newVersion);

    // 2) Preflight: verify publishable packages won't break on npm
    runPreflightChecks();

    // 3) Commit + tag new version
    createGitCommitAndTag(newVersion);

    // 4) Publish packages
    for (const target of packageTargets.filter((pkg) => pkg.publish)) {
        const pkgPath = path.resolve(target.dir);
        const manifestPath = path.join(pkgPath, "package.json");
        if (!fs.existsSync(manifestPath)) {
            console.warn(`Skipping publish for ${target.name}; missing ${manifestPath}`);
            continue;
        }
        const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
        if (manifest.private) {
            console.warn(
                `Skipping publish for ${target.name}; package.json is marked private`,
            );
            continue;
        }
        const accessFlag = target.access === "public" ? " --access public" : "";
        console.log(`Publishing ${target.name}@${newVersion}...`);
        run(`pnpm publish --no-git-checks${accessFlag}`, pkgPath);
    }
}

/**
 * Preflight checks to prevent broken publishes.
 * - Rejects workspace:/catalog: specifiers in runtime deps
 * - Ensures required built files exist (schemas/bin)
 * - Runs npm pack --dry-run for each publishable package
 */
function runPreflightChecks() {
    for (const target of packageTargets.filter((p) => p.publish)) {
        const pkgPath = path.resolve(target.dir);
        const manifestPath = path.join(pkgPath, "package.json");
        if (!fs.existsSync(manifestPath)) continue;
        const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));

        // 1) Disallow bad specifiers in runtime deps
        const badSpec = (deps?: Record<string, string>) =>
            !!deps && Object.values(deps).some((v) => /^workspace:|^catalog:/.test(String(v)));
        if (badSpec(manifest.dependencies) || badSpec(manifest.peerDependencies) || badSpec(manifest.optionalDependencies)) {
            throw new Error(
                `Preflight failed for ${target.name}: runtime deps contain 'workspace:' or 'catalog:' specifiers. Fix versions before releasing.`,
            );
        }

        // 2) Build once to produce artifacts we will check
        run("pnpm run build", pkgPath);

        // 3) Artifact checks per package
        if (target.name === "oplink") {
            const required = [
                path.join(pkgPath, "bin", "oplink.mjs"),
                path.join(pkgPath, "dist", "schema", "oplink-workflows.schema.json"),
                path.join(pkgPath, "dist", "schema", "oplink-servers.schema.json"),
                // presets removed
            ];
            for (const f of required) {
                if (!fs.existsSync(f)) {
                    throw new Error(`Preflight failed for oplink: missing built file ${f}`);
                }
            }
        }

        if (target.name === "@oplink/core") {
            const required = [
                // presets removed
            ];
            for (const f of required) {
                if (!fs.existsSync(f)) {
                    throw new Error(`Preflight failed for @oplink/core: missing built file ${f}`);
                }
            }
        }

        // 4) npm pack --dry-run must succeed
        run("npm pack --dry-run", pkgPath);
    }
}

// Get command line arguments
const args = process.argv.slice(2);
const subcmd = args[0] || "patch"; // Default to patch bump

if (subcmd === "preflight") {
    try {
        ensureCleanWorkingTree();
        runPreflightChecks();
        console.log("Preflight checks passed ✅");
        process.exit(0);
    } catch (err) {
        console.error("Preflight failed:", err);
        process.exit(1);
    }
} else {
    publishPackages(subcmd as any).catch((err) => {
        console.error(err);
        process.exit(1);
    });
}


--- AGENTS.md ---
Early development, no users. No backwards compatibility concerns. Do things RIGHT: clean, organized, zero tech debt. Never create compatibility shims.

--- CONTRIBUTE.md ---
# Contributing to oplink

Thank you for your interest in contributing to oplink! This document provides guidelines and instructions to help you get started with contributing to the project.

## Development Setup

### Prerequisites

- Node.js 18.x or higher
- pnpm 10.6.2 or higher

### Setting Up the Local Environment

1. Clone the repository:

```bash
git clone git@github.com:regenrek/oplink.git
cd oplink
```

2. Install dependencies:

```bash
pnpm install
```

3. Build the packages:

```bash
pnpm run build:packages
```

## Development Process

### Creating a New Feature

1. Create a new branch for your feature:

```bash
git checkout -b feature/your-feature-name
```

2. Make your changes and commit them:

```bash
git add .
git commit -m "feat: add your feature"
```

3. Push your branch to GitHub:

```bash
git push origin feature/your-feature-name
```

### Submitting a Pull Request

Go to the [oplink repository](https://github.com/regenrek/oplink) and create a pull request from your fork.

## Code Style and Guidelines

- Follow the existing code style and naming conventions.
- Write clear, concise commit messages that follow the [Conventional Commits](https://www.conventionalcommits.org/) specification.
- Add tests for your changes when applicable.
- Update documentation as needed.

## Development Scripts

- `pnpm build:packages`: Build all packages in the monorepo
- `pnpm build:templates`: Build all templates
- `pnpm build:docs`: Build documentation
- `pnpm dev:docs`: Run documentation in development mode
- `pnpm lint`: Run ESLint on the codebase
- `pnpm typecheck`: Run TypeScript type checking
- `pnpm build`: Build the main oplink package

## Release Process

The project follows [Semantic Versioning](https://semver.org/). When a new release is ready, maintainers will:

1. Update the version number in package.json.
2. Create a changelog entry.
3. Tag the release in Git.
4. Publish to npm using `pnpm publish-npm`.

## Issues and Discussions

Feel free to open issues for bugs, feature requests, or questions. For complex discussions, start a discussion in the repository's Discussions section.

Thank you for contributing!

## Links discovered
- [oplink repository](https://github.com/regenrek/oplink)
- [Conventional Commits](https://www.conventionalcommits.org/)
- [Semantic Versioning](https://semver.org/)

--- README.md ---
# Oplink

![Oplink Logo](public/oplink.png)


Create your own no-code workflows with MCP apps. Oplink combines multiple MCP servers into unified workflows that you define in simple YAML files.

✨ **Why Oplink?**
<br /><br />
🚀 *No-code agent workflows* — create your own agent workflows with just editing yaml files<br />
🧩 *One endpoint, many servers* — bundle any MCP Server like Chrome DevTools, shadcn, Context7, etc. behind a single MCP server entry.<br />
🛡️ *Guided prompts & schemas* — every workflow exposes typed parameters, instructions, and curated helper tools.<br />
💾 *Context-efficient discovery* — [mcporter](https://github.com/steipete/mcporter) caches tool schemas in-memory, so agents discover tools via `describe_tools` without flooding your MCP client with dozens of external commands. Only your curated workflows appear in the tool list.<br />
🧠 *Works in any MCP client* — Cursor, Claude Code, Codex, Windsurf, and friends can run complex flows without custom glue code.<br /><br />

Imagine you're debugging a frontend issue and need to:
- **Chrome DevTools** to inspect the browser, capture screenshots, and analyze network requests
- **shadcn** to understand component APIs and get the latest library documentation

![Example](public/workflow.jpg)

Without Oplink, you'd need to manually coordinate between multiple MCP servers, switching contexts and piecing together results. With Oplink, you define a single `frontend_debugging` workflow that orchestrates both servers in one call.

## Overview

Oplink transforms YAML-based workflow definitions into executable MCP tools. Unlike tools that only reference tool names in prompts, Oplink can actually execute external MCP tools that you wire in via a lightweight registry (`.mcp-workflows/servers.json`).

**Oplink combines multiple MCP servers into unified workflows.** Define prompts and tool sequences in YAML, wire in external MCP servers via a simple registry, and expose everything as a single MCP tool that works in any MCP client (Cursor, Claude, Windsurf, etc.).

### Example: Frontend Debugging Workflow

```yaml
frontend_debugging:
  description: "Debug frontend issues using Chrome DevTools and shadcn components"
  prompt: |
    Analyze the reported issue systematically.
    Use Chrome DevTools to inspect the browser state and capture diagnostics.
    Reference shadcn component documentation to understand the UI library.
  externalServers:
    - chrome-devtools
    - shadcn
```

One workflow, multiple servers, seamless execution. That's why Oplink exists.

## Installation

```bash
npx -y oplink@latest init
```

### Cursor Configuration

```json
{
  "mcpServers": {
    "oplink-get-docs": {
      "command": "npx",
      "args": [
        "oplink@latest",
        "server",
        "--config",
        "examples/deepwiki-demo/.mcp-workflows"
      ]
    },
    "oplink-frontend-debugging": {
      "command": "npx",
      "args": [
        "oplink@latest",
        "server",
        "--config",
        "examples/frontend-mcp-demo/.mcp-workflows"
      ],
      "env": {
        "FRONTEND_ROOT": "/path/to/oplink/examples/frontend-mcp-demo"
      }
    }
  }
}
```

### Custom Configuration

```json
{
  "mcpServers": {
    "oplink": {
      "command": "npx",
      "args": [
        "oplink@latest",
        "server",
        "--config",
        "/path/to/.mcp-workflows"
      ]
    }
  }
}
```

## Configuration

Create a `.mcp-workflows` directory and add YAML workflow files:

```yaml
debug_workflow:
  description: "Debug application issues"
  prompt: |
    Analyze the issue systematically.
    Gather logs and error information.
  externalServers:
    - your-server-alias
```

### MCP Server Registry

External tools are resolved through `.mcp-workflows/servers.json`. Each entry maps a friendly alias to an MCP server definition (stdio command or HTTP endpoint). Use `${ENV_VAR}` placeholders for secrets. When you run with `--config <dir>`, Oplink auto‑loads `.env` files from that directory before expanding placeholders (precedence: shell > `.env.{NODE_ENV}.local` > `.env.{NODE_ENV}` > `.env.local` > `.env`). You do not need a `mcporter.json` for Oplink to run.

```json
{
  "servers": {
    "context7": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp"],
      "env": { "CONTEXT7_TOKEN": "${CONTEXT7_TOKEN}" }
    },
    "grafana": {
      "type": "http",
      "url": "https://grafana.example.com/mcp",
      "headers": { "Authorization": "Bearer ${GRAFANA_TOKEN}" }
    }
  }
}
```

The alias (`context7`, `grafana`, etc.) becomes the `server` prefix when you reference an external tool inside a scripted workflow step (for example, `chrome-devtools:take_screenshot`). Startup fails if an alias referenced in your workflows is missing, the registry is malformed, or an environment placeholder cannot be resolved.

See `examples/context7-demo/` (Context7) and `examples/deepwiki-demo/` (DeepWiki) for ready-to-run setups that wire real MCP servers into Oplink via this registry + workflow pair.

### Auto Workflows (zero config)

To expose an MCP server without writing custom steps, declare a workflow with `externalServers`. Oplink now exposes one tool per workflow **and** ships a built-in `describe_tools` helper so agents can dynamically discover the proxied commands. The recommended flow is:

1. Call `describe_tools({ "workflow": "frontend_debugger" })` to retrieve the cached catalog (names, descriptions, JSON schemas, last refresh time).
2. Pick a tool from the response and invoke the workflow with `{ "tool": "name", "args": { ... } }`.

Every auto workflow prompt automatically appends a reminder to run `describe_tools` first, so you don't have to mention it manually—though you can still customize the prompt text if you want to provide extra context.

```yaml
frontend_debugger:
  description: "Chrome DevTools helper"
  prompt: |
    Use Chrome DevTools MCP tools (e.g., take_screenshot, list_network_requests).
    Provide {"tool": "name", "args": { ... }} when calling this workflow.
  externalServers:
    - chrome-devtools

shadcn_helper:
  description: "shadcn helper"
  prompt: |
    Use shadcn MCP tools to list/search components.
  externalServers:
    - shadcn

full_helper:
  description: "Chrome DevTools + shadcn"
  prompt: |
    Access Chrome DevTools and shadcn MCP tools from one workflow.
  externalServers:
    - chrome-devtools
    - shadcn
```

Call the workflow with:

- `tool`: the tool name (e.g., `take_screenshot` or `chrome-devtools:take_screenshot`).
- `server`: optional unless you configured multiple aliases (like `full_helper`) and didn’t prefix the tool.
- `args`: arguments object forwarded to the MCP tool.

```json
describe_tools({
  "workflow": "frontend_debugger"
})

frontend_debugger({
  "tool": "take_screenshot",
  "args": {
    "url": "https://example.com",
    "format": "png"
  }
})
```

`describe_tools` accepts optional filters such as `aliases`, `search`, `limit`, and `refresh`. Set `refresh: true` if you need to force a re-discovery after changing the upstream MCP server. Use auto workflows for quick wiring, then switch to scripted workflows (below) when you need curated flows, defaults, or multi-step orchestration.

#### Optional per-tool proxies

By default, Oplink keeps the MCP surface limited to your workflows plus helper utilities (`describe_tools`, `external_auth_setup`). If you want to expose *every* external MCP tool as its own MCP tool (e.g., `deepwiki.read_wiki_structure`) you can opt in by setting `OPLINK_AUTO_REGISTER_EXTERNAL_TOOLS=1` before starting the server (or by passing `autoRegisterExternalTools: true` when calling `createMcpServer`). This is mainly useful for debugging or when a client cannot call `describe_tools`. Similarly, the `oplink_info` helper is only registered when `OPLINK_INFO_TOOL=1` (or `includeInfoTool: true`) for troubleshooting builds.

### Scripted Workflow Steps

Modern Oplink workflows run entirely on the server: you declare the external steps to execute, and the MCP client only sees the high-level tool (e.g., `frontend_debugger`). Each step references an external MCP tool using the `alias:tool` format from `servers.json` and can template arguments from workflow parameters.

```yaml
take_screenshot:
  description: "Capture screenshots for docs or testing"
  runtime: scripted
  parameters:
    url:
      type: string
      required: true
    wait_for:
      type: string
      description: "Optional text to wait for"
    format:
      type: string
      enum: [png, jpeg, webp]
      default: png
  steps:
    - call: chrome-devtools:navigate_page
      args:
        type: url
        url: "{{ url }}"
        ignoreCache: false
    - call: chrome-devtools:wait_for
      requires: wait_for
      args:
        text: "{{ wait_for }}"
        timeout: 10000
    - call: chrome-devtools:take_screenshot
      args:
        fullPage: true
        format: "{{ format }}"
```

- `runtime: scripted` tells Oplink to execute these steps server-side via mcporter.
- `requires` skips the step unless the named parameter (or saved value) is truthy.
- Arguments can use `{{ paramName }}` templating.
- Only the workflow tool (`take_screenshot`) is exposed to the MCP client; the chrome-devtools helpers stay internal.
- Defaulted parameters like `format` keep the happy path simple (no extra args) while allowing overrides when you need a different image type.
- Add `quiet: true` to a step if you don't want the runner to emit "Step X" logs for that call (useful for screenshot steps that already return binary content).

### Prompt-Only Workflows

For simple workflows that only need a prompt without external tool execution, you can use parameter injection:

```yaml
thinking_mode:
  description: "Reflect on thoughts"
  parameters:
    thought:
      type: "string"
      description: "The thought to reflect upon"
      required: true
    context:
      type: "string"
      description: "Additional context"
  prompt: |
    Deeply reflect upon: {{ thought }}
    Consider this context: {{ context }}
    Analyze implications and tradeoffs.
```

## Examples

The repository includes example configurations under `examples/` showing both prompt-only and scripted workflows. Use these as references when creating your own YAML in `.mcp-workflows/`.

## External Tool Integration

Oplink uses [mcporter](https://github.com/steipete/mcporter) under the hood to connect to external MCP servers, but it reads the registry from `.mcp-workflows/servers.json` in your chosen `--config` directory.
1. Define servers in `.mcp-workflows/servers.json` (see the examples above)
2. Reference tools as `server:tool` inside scripted workflow steps
3. Only the workflow tools themselves are exposed to the MCP client; helper tools remain internal

**Tool Call Flow:**
```
MCP Client → Oplink → mcporter Runtime → External MCP Server → Result
```

External tools are discovered at startup, cached with schema hashes, and exposed through the `describe_tools` helper instead of flooding the MCP client with dozens of proxied commands. The cache automatically refreshes when it expires, and you can trigger a manual refresh by calling `describe_tools({ "workflow": "name", "refresh": true })` if the upstream server changes.

See also:
- Advanced: How Oplink Uses mcporter → `docs/oplink-docs/content/5.advanced/3-mcporter.md`
- Advanced: Auth for External MCP Servers (API Key, OAuth) → `docs/oplink-docs/content/5.advanced/4-authentication.md`

### Connecting to hosted MCP servers (OAuth)

Hosted providers like Linear expose MCP servers over HTTPS/SSE and expect an OAuth flow. mcporter 0.5+ already handles the browser/device dance, so you just need one config entry per server:

```json
"linear": {
  "type": "stdio",
  "command": "npx",
  "args": ["-y", "mcp-remote", "https://mcp.linear.app/mcp"],
  "auth": "oauth",
  "clientName": "oplink-linear-demo",
  "oauthRedirectUrl": "http://127.0.0.1:43115/callback",
  "tokenCacheDir": "./.tokens/linear",
  "env": {
    "MCP_REMOTE_CLIENT_ID": "${LINEAR_CLIENT_ID}",
    "MCP_REMOTE_CLIENT_SECRET": "${LINEAR_CLIENT_SECRET}"
  }
}
```

Notes:

1. `type: "stdio"` + `npx mcp-remote` lets Oplink spawn the hosted server even though it lives on HTTPS.
2. mcporter caches refresh tokens under `tokenCacheDir`, so the OAuth prompt only happens once.
3. Skip the client ID/secret prompts if you prefer dynamic registration—mcporter will open a browser during the first tool call.
4. Run `pnpm bootstrap:linear` to copy the example config and (optionally) inject your credentials into `examples/linear-discord-demo/.mcp-workflows/servers.json`.

To inspect the tools exposed by any alias, reuse the same config directory:

```bash
npx mcporter list linear --config examples/linear-discord-demo/.mcp-workflows

For Discord in the demo, export `DISCORD_BOT_TOKEN` in your shell; Oplink maps it to `DISCORD_TOKEN` for the MCP server defined in `examples/linear-discord-demo/.mcp-workflows/servers.json`.
```

## Requirements

- Node.js 18+ or 20+
- Optional: mcporter CLI for local inspection (`npx mcporter list <alias> --config path/to/.mcp-workflows`)
- MCP client (Cursor, Claude Desktop, etc.)

## Troubleshooting

- Missing `FRONTEND_ROOT` (shadcn): set `export FRONTEND_ROOT=$(pwd)/examples/frontend-mcp-demo` or set it under your MCP client entry's `env` block.
- Chrome won’t launch: ensure Chrome is installed and starts locally. For remote/debugging Chrome, launch it separately and update the Chrome DevTools server flags per its docs.
- No tools appear: confirm `--config` points to the intended `.mcp-workflows` directory and your IDE picked up the MCP server entry.
- Tool catalog looks stale: run `describe_tools({ "workflow": "name", "refresh": true })` to force a re-discovery after changing the upstream MCP server.

## Development

```bash
# Install dependencies
pnpm install

# Build packages
pnpm build

# Run tests
pnpm test

# Start development server
cd packages/oplink
pnpm dev
```

## Definition

Oplink is an MCP server that orchestrates workflows by combining prompts with external MCP tool execution. It bridges your workflow definitions with mcporter-configured MCP servers, enabling automatic tool discovery and execution.

## Credits

- Initial idea inspired by [mcpn](https://github.com/regenrek/mcpn), developed in collaboration with [@tedjames](https://github.com/tedjames)
- Using [mcporter](https://github.com/steipete/mcporter) code-generation toolkit for mcp by [@steipete](https://github.com/steipete)

## License

MIT

## Repository

https://github.com/instructa/oplink
- Chrome DevTools screenshot errors: if a workflow calls `chrome-devtools:take_screenshot` without specifying `format`, DevTools rejects the request. The provided examples set a default (`png`) and let you override it via the `format`/`screenshot_format` parameter.


## Links discovered
- [Oplink Logo](https://github.com/regenrek/oplink-mcp/blob/main/public/oplink.png)
- [mcporter](https://github.com/steipete/mcporter)
- [Example](https://github.com/regenrek/oplink-mcp/blob/main/public/workflow.jpg)
- [mcpn](https://github.com/regenrek/mcpn)
- [@tedjames](https://github.com/tedjames)
- [@steipete](https://github.com/steipete)

--- stderr.txt ---


--- stdout.txt ---


--- time.txt ---
node:internal/modules/esm/resolve:275
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/kregenrek/projects/mcp/oplink/node_modules/.pnpm/rolldown@1.0.0-beta.47/node_modules/rolldown/dist/cli-setup.mjs' imported from /Users/kregenrek/projects/mcp/oplink/node_modules/.pnpm/rolldown@1.0.0-beta.47/node_modules/rolldown/bin/cli.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:275:11)
    at moduleResolve (node:internal/modules/esm/resolve:932:10)
    at defaultResolve (node:internal/modules/esm/resolve:1056:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:654:12)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:603:25)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:586:38)
    at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:242:38)
    at ModuleJob._link (node:internal/modules/esm/module_job:135:49) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///Users/kregenrek/projects/mcp/oplink/node_modules/.pnpm/rolldown@1.0.0-beta.47/node_modules/rolldown/dist/cli-setup.mjs'
}

Node.js v22.12.0
real 0.73
user 0.59
sys 0.12


--- .cursor/commands/code-review.md ---
<code_review_prompt version="1.0">
  <user_input>
  $1
  </user_input>

  <role>
    You are a senior software engineer, security reviewer, and performance specialist. Review the provided change with a focus on correctness, security, performance, integration, test quality, and long-term maintainability. Be precise, cite file paths and line ranges, and prioritize risks that could impact users, data, uptime, or developer velocity.
  </role>

  <inputs_expected>
- Change summary (what/why/risk): {CHANGE_SUMMARY}
- Unified diff or patches: {DIFF}
- Key files (new/modified/deleted): {FILES}
- Build / test logs & coverage: {CI_LOGS} {COVERAGE_SUMMARY}
- Runtime logs or traces (if any): {RUNTIME_LOGS}
- Environment & deployment context: {ENVIRONMENT}
- API schemas/contracts (OpenAPI/GraphQL/Protobuf): {API_SCHEMAS}
- DB schema/migrations: {DB_MIGRATIONS}
- Dependency manifests/lockfiles: {DEPENDENCIES}
- Non-code assets (Dockerfiles, IaC, configs, feature flags): {NONCODE_ASSETS}
- Design notes/ADRs/performance budgets/threat model: {DESIGN_DOCS}
  </inputs_expected>

  <objectives>
- Identify correctness defects, code smells, and anti-patterns.
- Surface exploitable security issues and data-protection risks.
- Spot performance/regression risks and complexity hotspots.
- Check integration points (APIs, events, DB, configs, CI/CD, infra) for compatibility and rollout safety.
- Assess tests for sufficiency, signal, reliability, and coverage.
- Recommend minimal, safe, high-leverage improvements and monitoring.
  </objectives>

  <severity_rubric>
- BLOCKER: Exploitable security flaw, data loss risk, broken build/deploy, user-impacting crash, irreversible migration risk, leaked secrets.
- HIGH: Likely prod incident or major regression; authz/auth gaps; significant perf degradation; schema incompatibility.
- MEDIUM: Correctness edge cases; non-exploitable but risky pattern; moderate perf concerns; flaky tests.
- LOW: Maintainability, readability, style, minor test gaps; suggestions.
- NIT: Optional polish.
  </severity_rubric>

  <tasks>
- Scope & Impact: Map all affected files/modules and why each is implicated. Note transitive and runtime impact (build, deploy, config, data).
- Root Cause / Risk Analysis: Explain the change intent, risks introduced, and any hidden assumptions or environmental dependencies.
- Security Review: Use the checklist below; escalate any secret exposure, injection, auth/authz flaws, SSRF/XXE/path traversal, insecure deserialization, command execution, mass assignment, CSRF/XSS, prototype pollution, weak crypto, missing TLS verification, permissive CORS, logging of secrets/PII, dependency vulns, or IaC/container misconfigurations.
- Performance Review: Identify complexity issues, N+1 queries, unbounded loops, memory churn/leaks, blocking I/O on hot paths, missing indexes, cache misuse, chatty network calls, unnecessary allocations/boxing, and concurrency contention.
- Integration Review: Validate API schema changes, versioning, backward/forward compatibility, idempotency, retries/timeouts/circuit breakers, feature-flag rollout, DB migrations (order/locking/rollback), message/event contracts, and config drift.
- Testing Review: Evaluate unit/integration/e2e tests, coverage, negative/property-based cases, concurrency/time-dependent tests, fixture health, determinism, and flakiness risk. Propose a targeted test plan.
- Observability & Operations: Check log levels, PII in logs, correlation/trace IDs, metrics and alerts, runbooks. Recommend what to monitor post-merge.
- Documentation & DX: Flag missing or outdated README/CHANGELOG/ADRs/API docs/config comments/schema diagrams. Note onboarding and maintenance friction.
- Minimal, Safe Fix: Propose the smallest viable change to eliminate blockers/high risks. Include tests and rollout/rollback steps.
  </tasks>

  <detailed_checklist>
    <category name="Correctness & Code Smells">
- Duplicate code / long methods / large classes / deep nesting.
- Leaky abstractions, tight coupling, poor cohesion, improper layering.
- Dead code, unused variables/imports, TODOs that should be addressed now.
- Non-idempotent operations where idempotency is required.
- Edge cases: null/empty/NaN/overflow/encoding/timezone/locale.
- Concurrency: shared state, race conditions, improper locking, async misuse.
    </category>

    <category name="Security">
- Secrets in code/logs/env/examples; credential handling, key rotation, KMS/secret manager usage.
- Input validation & output encoding; SQL/NoSQL/LDAP/OS injection; XSS (reflected/stored/DOM); CSRF.
- AuthN/AuthZ: broken access control, least privilege, multi-tenant boundaries, insecure direct object references.
- SSRF/XXE/path traversal/file upload validation; sandboxing for untrusted inputs.
- Crypto: algorithms, modes, IVs, nonces, randomness, key sizes, cert pinning/TLS verification.
- CORS/security headers (CSP/HSTS/X-Frame-Options/SameSite), cookie flags.
- Dependency & supply chain: pinned versions, known CVEs, pre/post-install scripts, integrity checks.
- IaC/Containers: public buckets, open security groups (0.0.0.0/0), missing encryption, root containers, mutable latest tags.
- Data protection & privacy: PII/PHI handling, minimization, retention, encryption at rest/in transit.
    </category>

    <category name="Performance">
- Time/space complexity, hot-path allocations, unnecessary synchronization.
- N+1 queries, missing DB indexes, inefficient joins, full scans, pagination vs. streaming.
- Caching: invalidation, eviction, key design, stampedes.
- Network patterns: chattiness, batching, compression, timeouts, backoff.
- Client-side perf (if UI): bundle size/regressions, critical path, images/fonts.
    </category>

    <category name="Integration & Rollout Safety">
- Backward/forward compatibility; versioned contracts; consumer-producer alignment.
- DB migrations: zero-downtime (expand/migrate/contract), locks, data backfills, rollback plan.
- Feature flags: default off, kill switch, gradual rollout, owner/expiry.
- Resilience: retries with jitter, timeouts, circuit breakers, idempotency keys.
- Config changes: validation, defaults, environment parity, secrets not in plain text.
- CI/CD: reproducibility, cache safety, test gates, artifact signing.
    </category>

    <category name="Testing & Quality Signals">
- Tests exist for new behavior and regressions; meaningful assertions.
- Coverage on critical branches/edge cases; mutation score (if available).
- Isolation: minimal mocking vs. over-mocking; flaky patterns (sleep-based timing, order reliance).
- Property-based/fuzz tests for parsers/validators/serializers.
- Load/soak tests where perf risk exists; snapshot tests stability (if UI).
    </category>

    <category name="Docs, Observability, Accessibility, i18n">
- README/CHANGELOG/ADR/API docs updated; code comments for non-obvious logic.
- Logs/metrics/traces with actionable context; PII redaction; alert thresholds.
- Accessibility (if UI): semantics, focus order, labels, contrast, keyboard nav, ARIA use.
- i18n/l10n: hard-coded strings, pluralization, date/time/number formats.
    </category>
  </detailed_checklist>

  <output_requirements>
    <instructions>
- Produce a concise but comprehensive report.
- Group findings by category and severity.
- Reference exact file paths and line ranges (e.g., src/foo/bar.py:120–147).
- Include brief code excerpts only as necessary (≤20 lines per finding).
- Prefer specific, minimal fixes and tests that maximize risk reduction.
- If information is missing, state the assumption and its impact.
    </instructions>
  </output_requirements>

  <report_skeleton>
- Summary:
  - What changed: <concise overview>
  - Top risks: <1-3 bullets>
  - Approval: <approve|comment|request_changes|blocker>

- Affected files:
  - <path> — <reason> (<added|modified|deleted>)

- Root cause & assumptions:
  - <analysis>
  - Assumptions: <items>

- Findings (repeat per finding):
  - [<severity>] [<category>] <short title>
    - Where: <file:line-range>
    - Evidence: <brief snippet_trace>
    - Impact: <what breaks_who is affected>
    - Standards: <CWE/OWASP/Policy refs>
    - Repro: <steps>
    - Recommendation: <minimal fix>
    - Tests: <tests to add_update>

- Performance:
  - Hotspots: <items>
  - Complexity notes: <items>
  - Bench/Monitoring plan: <how to measure & watch>

- Integration:
  - API/contracts: <compat/versioning/idempotency>
  - DB migrations: <expand-migrate-contract, locks, rollback>
  - Feature flags & rollout: <plan/kill switch_owner>
  - Resilience: <timeouts/retries/circuits>
  - Rollback plan: <how to revert safely>

- Testing:
  - Coverage: <statements_branches_critical_paths>
  - Gaps: <cases>
  - Flakiness risks: <items>
  - Targeted test plan: <Given_When_Then bullets>

- Docs & Observability:
  - Docs to update/create: <paths/sections>
  - Logs/Metrics/Traces/Alerts: <plan>
  - Runbook: <updates>

- Open questions:
  - <items>

- Final recommendation:
  - Decision: <approve|comment|request_changes|blocker>
  - Must-fix before merge: <items>
  - Nice-to-have post-merge: <items>
  - Confidence: <low|medium|high>
  </report_skeleton>

  <process_notes>
- Prioritize BLOCKER/HIGH issues. If any are found, set approval to “blocker” or “request_changes”.
- Favor minimal, safe changes and targeted tests over broad refactors (unless safety demands it).
- If diff is very large, focus on high-risk/new code paths, public interfaces, security-critical modules, and hot paths.
- Reference concrete files/lines. Keep code excerpts minimal (≤20 lines). Do not rewrite large code blocks.
- If required inputs are missing (e.g., DB migration script or API schema), flag as a risk and propose what is needed.
  </process_notes>

  <constraints>
- DO NOT write or generate full code implementations in this review. Provide patch outlines, pseudocode, or stepwise instructions only.
- Maintain confidentiality: if a secret or sensitive data appears, describe it without reproducing it verbatim.
  </constraints>

  <success_criteria>
- Findings are specific, actionable, and ordered by severity and blast radius.
- Every high-risk change has a minimal fix and a concrete test/monitoring plan.
- Output follows the provided report skeleton (markdown text only).
  </success_criteria>

  <reminder>DON'T CODE YET.</reminder>
</code_review_prompt>


--- .cursor/commands/code-review-low.md ---
<task>
$1
</task>

## Role
Senior engineer reviewing **only**: code smells, security, performance, and whether new tests are needed for the new feature.

## Inputs
- {CHANGE_SUMMARY}
- {DIFF} + {FILES}
- {CI_LOGS} {COVERAGE_SUMMARY} (optional)
- {ENVIRONMENT} {API_SCHEMAS} {DB_MIGRATIONS} {DEPENDENCIES} (if relevant)

## What to check
### Code Smells
- Duplicates, long methods, deep nesting, dead code, unused imports
- Leaky abstractions, tight coupling, improper layering
- Edge cases: null, empty, timezones, encodings
- Concurrency misuse and non-idempotent ops where required

### Security
- Secrets in code/logs; proper secret management
- Input validation and output encoding; SQL/NoSQL/OS injection; XSS/CSRF
- AuthN/AuthZ and multi-tenant boundaries
- SSRF/XXE/path traversal/file upload validation
- Crypto choices; TLS verification; CORS and security headers
- Dependency CVEs and supply-chain risks; IaC/container misconfig

### Performance
- Time/space complexity; hot-path allocations; blocking I/O
- N+1 queries; missing indexes; inefficient joins; full scans
- Caching correctness and stampedes
- Chatty network calls; batching; timeouts; backoff
- Client bundle size and critical path (if UI)

### Tests needed
- Does new behavior have unit/integration/e2e tests
- Edge cases, negative cases, concurrency/time-based cases
- Minimal test plan to guard the change

## Output format
- **Summary**: what changed, top 1–3 risks, **Decision**: approve | request_changes | blocker
- **Findings** grouped by **Smell | Security | Performance | Tests**
  - `[severity] <title>`  
    - Where: `<file:line-range>`  
    - Impact: `<who/what is affected>`  
    - Recommendation: `<smallest safe fix>`  
    - Tests: `<tests to add/update>`
- Cite exact files and line ranges. Keep code excerpts ≤20 lines.

## Constraints
- No full implementations. Pseudocode or patch outline only.
- If data is missing, state the assumption and risk.

--- .cursor/commands/create-finding.md ---
summarize everythign from our chat.

write a bug report.
and add affected files. 

use linear create_issue 

<issue_template>
  - title
  - description
  - priority
</issue_template>

--- .cursor/commands/create-finding-linear.md ---
create a bug in linear and summarize it

--- .cursor/commands/create-pr.md ---
create a pr with gh cli ..... 

--- .cursor/commands/create-story.md ---
---
description: use this ALWAYS to create/update a story
globs: docs/stories/*.md
alwaysApply: false
---

# Sstep
1. create the <story>
2. use <send_to_linear>

<story>
Use this template to create a new story for tracking in the `docs/stories` directory. 

# User Story: {{ID}} - {{TITLE}}

## Status: {{STATUS}}  
*(Valid values: TODO, IN PROGRESS, DONE)*

## Description:

As a {{USER_TYPE}}, I want {{FEATURE}} so that {{REASON}}.

## Acceptance Criteria:

- [ ] {{CRITERION_1}}
- [ ] {{CRITERION_2}}
- [ ] ...

## General Tasks:

- [ ] All requirements from the plan are implemented
- [ ] Code follows project style guidelines
- [ ] Tests are written and passing
- [ ] Documentation is updated
- [ ] Performance considerations addressed
- [ ] Security considerations addressed
- [ ] Code has been reviewed

## Sub Tasks:

- [ ] {{SUB_TASK_1}} - Status: {{SUB_TASK_1_STATUS}}

## Estimation: {{ESTIMATION}} story points  
*(Note: One step is 1 story point, which equals 1 day of work for a senior developer)*

## Developer Notes:
*(Note: Add here important learnings, necessary fixes, all other devs need to know to proceed)*

- {{NOTE_1}}
- {{NOTE_2}}
- ...

</story>

<send_to_linear>
use linear mcp create task 
</sent_to_linear>

--- .cursor/commands/get-logs-from-grafana.md ---


--- .cursor/commands/kill-port-3000.md ---
kill port 1420

--- .cursor/commands/problem-analyzer.md ---
Tasks:
1) Locate all files/modules affected by the issue. List paths and why each is implicated.
2) Explain the root cause(s): what changed, how it propagates to the failure, and any environmental factors.
3) Propose the minimal, safe fix. Include code-level steps, side effects, and tests to add/update.
4) Flag any missing or outdated documentation/configs/schemas that should be updated or added (especially if code appears outdated vs. current behavior). Specify exact docs/sections to create or amend.

Output format:
- Affected files:
  - <path>: <reason>
- Root cause:
  - <concise explanation>
- Proposed fix:
  - <steps/patch outline>
  - Tests:
- Documentation gaps:
  - <doc_section_what_to_update_add>
- Open questions/assumptions:
  - <items>
  
  
 DON'T CODE YET.

--- .cursor/commands/pull-tickets.md ---
use linear get all issues assignee = kevin
and save each of them to my stories/<issuename>
and save the name to variable {{ISSUE_PATH_FILE}}


## STEP 1.

use the following layout

<layout>
  <full_ticket_description>
    {{ ADD HERE FULL TICKET DESC }}
  </full_ticket_description>


  <explanation>
   ... Add here an explanation according to my current codebase...
   which files do i need to touch.
  </explanation>
</layout>


## STEP 2

Call `codex "/plan-review read {{ISSUE_PATH_FILE}} and save the review as new plan in docs/stories/{{ISSUE_PATH_FILE}}-reviewed.md`



--- packages/cli/README.md ---
# Workflows MCP v0.1.0

Built by [@tedx_ai](https://x.com/tedx_ai)

Workflows MCP is a Model Context Protocol (MCP) server that allows you to orchestrate / combine many prompts + MCP servers into compound MCP prompting tools.

Think of it like a dynamic prompting library that you can easily share and version control through yaml files that also lets your define how to best use many MCP tools across many MCP servers for specific tasks.

## Overview

The key to effective MCP use is to know when/how to use the right tools. Workflows MCP helps make this process MUCH easier and faster. With Workflows MCP, you can:

✅ Combine prompts with MCP servers into reusable & easy to edit/share/organize workflows

✅ Trigger workflows with custom commands like: "enter debugger mode" or "use thinking mode"

✅ Define custom strategies for how to use many tools in a workflow (sequential or situational/dynamic)

✅ Easily onboard your team to the best prompts + ways to use many MCP tools w/ version control.

_Bring your own workflows as simple YAML; share and version them with your team._

### So why use this?

The goal of this project is to turn prompts into programmable tools and rules for AI in a code editor more deterministic while reducing the overall amount of token use across all requests. Since stuffing a global set of rules for AI can eat up your context window, using MCP to route to prompts and workflows can help reduce the overall amount of tokens used across all requests.

Workflows for MCP are also **incredibly** powerful! For instance, you can create an **"production incident workflow"** tool that has a special system prompt to reason about an incident in a particular way and then use a series of tools in a specific order to analyze + potentially resolve the incident. This may involve things like:

- Gathering logs across many MCP tools for Kubernetes, Cloudwatch, Splunk, etc
- Checking Github issues or a vector database for similar issues in the past
- Perform web searches for useful information on the web
- Analyze your code with all the gathered context from the prior steps
- Write a detailed incident report with all the context from the prior steps
- Gather input from you to better understand the problem space
- Implement a fix for issue if possible

And since these workflows are all defined in YAML and can be referenced from anywhere on your machine, you can **easily** rally your team around when/how to use the best prompts and sequences of MCP tools for specific tasks 🦾

## Features

### 📝 Custom Configuration

- Load custom configs from multiple YAML files in a `.workflows` or `.mcp-workflows` directory
- Easily save these yaml files to a git repo and share with your team
- Since MCP servers can have custom configs for each project, you can easily pick and choose which configs to use for each project - setting up custom workflow folders for each project
- Support for typed parameter inputs in tool configurations makes building custom MCP prompting tools a breeze

### 🚀 Example Workflows

Use the examples in this repo (or your own) to seed your `.mcp-workflows/` directory. You can define prompt‑only tools or scripted multi‑step workflows that call external MCP servers.

## Installation

Install the MCP server into an MCP client using the following command or JSON:

```bash
npx -y @agentdesk/workflows-mcp@latest
```

If using JSON in Cursor to setup your MCP server, you can use the following config:

```json
{
  "mcpServers": {
    "workflows-mcp": {
      "command": "npx",
      "args": ["-y", "@agentdesk/workflows-mcp@latest"]
    }
  }
}
```

To provide custom configurations, you can use the `--config` flag to point to a directory containing YAML configuration files. The directory must be named `.workflows` or `.mcp-workflows` as so:

```bash
npx @agentdesk/workflows-mcp@latest --config /path/to/.workflows
```

Here's what this would look like in a Cursor config all combined:

```json
{
  "mcpServers": {
    "workflows-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@agentdesk/workflows-mcp@latest --config /path/to/.workflows"
      ]
    }
  }
}
```

Note:

- _If you update your config, you must refresh the MCP tool_
- _If refreshing doesn't work, make sure your config is valid YAML_
- _If you're still having issues, then try removing & renaming the MCP tool in your client_
- _If no config is provided, the server will start without user workflows._
- _If you're still not able to get this working, open an issue ticket_

## Custom Workflow Configs

Create a `.workflows` or `.mcp-workflows` directory in your project and add YAML configuration files with any name (must end with `.yaml` or `.yml`).

### Example Configuration Files

#### Basic Workflow Configuration

```yaml
workflow_name:
  description: "Description of what this workflow does"
  prompt: |
    Enter your multi-line
    prompt here like this
  toolMode: "situational" # can be omitted as it's the default
  tools: "analyzeLogs, generateReport, validateFindings"
```

#### Tools Configuration Styles

You can define tools in several ways.

Here's an example of a tool that debugs a problem in a codebase with tools used situationally / as needed:

```yaml
web_debugger_mode:
  description: Debug my codebase my web application with browser logs and BrowserTools MCP
  prompt: |
    Deeply reflect upon all of this and think about why this isn't working. Theorize 4-6 different possible sources of the problem.

    Then, deeply reason about the root cause and distill your theories down to the 1-2 most probable sources of the problem before suggesting next steps.
  tools: getConsoleLogs, getConsoleErrors, getNetworkLogs, getNetworkErrors, takeScreenshot
```

This will return the following MCP response:

```
Deeply reflect upon all of this and think about why this isn't working. Theorize 4-6 different possible sources of the problem.


## Available Tools
Use these tools as needed to complete the user's request:

- getConsoleLogs
- getConsoleErrors
- getNetworkLogs
- getNetworkErrors
- takeScreenshot

After using each tool, return a 'Next Steps' section with a list of the next steps to take / remaining tools to invoke along with each tool's prompt/description and 'optional' flag if present.
```

#### Parameter Injection

A powerful feature is the ability to inject parameters into your prompts using the `{{ parameter_name }}` syntax:

```yaml
custom_mode:
  description: "Workflow with parameter injection"
  parameters:
    thought:
      type: "string"
      description: "A thought to deeply reflect upon"
      required: true
    idea:
      type: "string"
      description: "An additional idea to consider"
  prompt: |
    Deeply reflect upon the provided thought.
    Here's the thought: {{ thought }}

    Additional idea to consider: {{ idea }}

    Reflect upon the implications/tradeoffs it may have as it relates to my current goals.
```

Parameters are automatically validated based on their type definitions and injected into your prompts at runtime.

#### Sequential Tool Configuration

In sequential mode, tools are executed in a specific order:

```yaml
web_debugger_mode:
  description: Debug my codebase my web application with browser logs and BrowserTools MCP
  prompt: |
    Deeply reflect upon all of this and think about why this isn't working. Theorize 4-6 different possible sources of the problem.
  toolMode: sequential
  tools: getConsoleLogs, getConsoleErrors, getNetworkLogs, getNetworkErrors, takeScreenshot
```

This will return the following MCP response:

```
Deeply reflect upon all of this and think about why this isn't working. Theorize 4-6 different possible sources of the problem.

## Available Tools
If all required user input/feedback is acquired or if no input/feedback is needed, execute this exact sequence of tools to complete this task:

1. getConsoleLogs
2. getConsoleErrors
3. getNetworkLogs
4. getNetworkErrors
5. takeScreenshot

After using each tool, return a 'Next Steps' section with a list of the next steps to take / remaining tools to invoke along with each tool's prompt/description and 'optional' flag if present.
```

### Advanced Tool Configuration

Includes ability to define prompts for each tool and an optional flag to indicate if the tool is optional or not in the sequence:

```yaml
deep_thinking_mode:
  description: Reflect on a thought and produce a reflection/new set of thoughts
  parameters:
    thought:
      type: string
      description: A thought to deeply reflect upon
      required: true
  prompt: |
    Deeply reflect upon the provided thought.
    Reflect upon the implications/tradeoffs it may have as it relates to my current goals, challenges and our conversation.
    Do not change anything in our system, just return some thoughts/considerations/analysis based on your reflection of the provided thought.
  toolMode: "sequential"
  tools:
    analyze_thought: analyze a previously generated thought
    explore_perspectives: think about additional perspectives given the analysis
    apply_findings:
      propmt: implement the findings of the analysis
      optional: true
```

### Configuration Structure

Each YAML file should contain a mapping of tool names to their configuration. Configurations are loaded from user-defined files in `.workflows` or `.mcp-workflows`.

### Basic Tool Configuration

For each tool, you can specify:

- `name`: Optional name override for the registered tool (default is the config key)
- `description`: Description of what the workflow/tool does
- `prompt`: Custom prompt
- `context`: Additional context to append to the prompt
- `tools`: Array or object of tools available in this mode, with flexible definition styles
- `toolMode`: Mode of tool execution, either "sequential" or "situational" (defaults to "situational")
- `parameters`: Object mapping of parameters as input to the tool - supports template injection using {{ parameter_name }}
- `disabled`: Boolean to disable the tool

### Input Parameter Configuration

Tools can accept typed parameters that an MCP Client / agent can provide to improve reasoning capabilities for your prompt/workflow. These inputs are automatically converted to Zod schemas for validation and type safety. Each parameter is defined with the following properties:

| Property      | Description                                                            | Required                 |
| ------------- | ---------------------------------------------------------------------- | ------------------------ |
| `type`        | Data type: "string", "number", "boolean", "array", "object", or "enum" | Yes                      |
| `description` | Human-readable description of the parameter                            | No                       |
| `required`    | Whether the parameter is required                                      | No (defaults to `false`) |
| `default`     | Default value if not provided                                          | No                       |
| `enum`        | Array of valid values (required for `enum` type only)                  | Yes (for `enum` type)    |
| `items`       | For array types, defines the type of items in the array                | No                       |
| `properties`  | For object types, defines the properties of the object                 | No                       |

#### Example Input Parameter Types

```yaml
# String parameter
name:
  type: "string"
  description: "User's name"
  required: true

# Number parameter with default
limit:
  type: "number"
  description: "Maximum items to return"
  default: 10

# Boolean parameter with default
includeArchived:
  type: "boolean"
  description: "Include archived items"
  default: false

# Enum parameter with predefined options
sortOrder:
  type: "enum"
  enum: ["asc", "desc"]
  description: "Sort direction"
  default: "asc"

# Array parameter with item type
tags:
  type: "array"
  description: "List of tags to filter by"
  items:
    type: "string"
    description: "A tag value"

# Object parameter with nested properties
filters:
  type: "object"
  description: "Complex filter object"
  properties:
    status:
      type: "enum"
      enum: ["active", "inactive", "pending"]
      description: "Status filter"
    dateRange:
      type: "object"
      properties:
        start:
          type: "string"
          description: "Start date"
        end:
          type: "string"
          description: "End date"
```

### Configuration Loading

The system loads configurations from your `.workflows` or `.mcp-workflows` directory.

When merging configurations:

- Tool arrays are concatenated rather than replaced
- Other properties are overridden by the user-defined configuration
- Parameters are merged with user-defined parameters taking precedence

## How It Works

Workflows MCP operates through a sophisticated configuration and tool registration system:

1. **Configuration Loading and Merging**

   - Loads user-defined configurations from `.workflows` or `.mcp-workflows` directories
   - Supports both sequential and situational tool execution modes
   - Supports both sequential and situational tool execution modes

2. **Tool Registration and Validation**

   - Each tool is dynamically registered with the MCP server based on the merged configuration
   - Tools can be customized with:
     - Custom names and descriptions
     - Typed parameters with validation
     - Custom prompts
     - Sequential or situational execution strategies
   - Validates tool configurations and parameters at registration time

3. **Parameter Handling**

   - Parameters are defined using a type-safe configuration system
   - Supports multiple parameter types: string, number, boolean, array, object, and enum
   - Automatically converts parameter definitions to Zod schemas for runtime validation
   - Provides automatic validation of parameter values during tool execution

4. **Prompt Management**

   - Manages prompts through a flexible system that supports custom user-defined prompts
     - Additional context injection
     - Dynamic tool availability based on mode
   - Supports both static and dynamic prompt generation based on configuration

5. **Error Handling and Debugging**
   - Comprehensive error handling during configuration loading and tool execution
   - Detailed logging for debugging and troubleshooting
   - Graceful fallbacks when configurations or tools are missing
   - Runtime validation of tool inputs and configurations

This architecture allows for powerful, type-safe interactions between tools and clients while maintaining flexibility through configuration-driven customization.

## License

MIT


## Links discovered
- [@tedx_ai](https://x.com/tedx_ai)

--- packages/oplink/README.md ---
# Workflows MCP v0.1.0

Built by [@tedx_ai](https://x.com/tedx_ai)

Workflows MCP is a Model Context Protocol (MCP) server that allows you to orchestrate / combine many prompts + MCP servers into compound MCP prompting tools.

Think of it like a dynamic prompting library that you can easily share and version control through yaml files that also lets your define how to best use many MCP tools across many MCP servers for specific tasks.

## Overview

The key to effective MCP use is to know when/how to use the right tools. Workflows MCP helps make this process MUCH easier and faster. With Workflows MCP, you can:

✅ Combine prompts with MCP servers into reusable & easy to edit/share/organize workflows

✅ Trigger workflows with custom commands like: "enter debugger mode" or "use thinking mode"

✅ Define custom strategies for how to use many tools in a workflow (sequential or situational/dynamic)

✅ Easily onboard your team to the best prompts + ways to use many MCP tools w/ version control.

_Bring your own workflows as simple YAML; share and version them with your team._

### So why use this?

The goal of this project is to turn prompts into programmable tools and rules for AI in a code editor more deterministic while reducing the overall amount of token use across all requests. Since stuffing a global set of rules for AI can eat up your context window, using MCP to route to prompts and workflows can help reduce the overall amount of tokens used across all requests.

Workflows for MCP are also **incredibly** powerful! For instance, you can create an **"production incident workflow"** tool that has a special system prompt to reason about an incident in a particular way and then use a series of tools in a specific order to analyze + potentially resolve the incident. This may involve things like:

- Gathering logs across many MCP tools for Kubernetes, Cloudwatch, Splunk, etc
- Checking Github issues or a vector database for similar issues in the past
- Perform web searches for useful information on the web
- Analyze your code with all the gathered context from the prior steps
- Write a detailed incident report with all the context from the prior steps
- Gather input from you to better understand the problem space
- Implement a fix for issue if possible

And since these workflows are all defined in YAML and can be referenced from anywhere on your machine, you can **easily** rally your team around when/how to use the best prompts and sequences of MCP tools for specific tasks 🦾

## Features

### 📝 Custom Configuration

- Load custom configs from multiple YAML files in a `.workflows` or `.mcp-workflows` directory
- Easily save these yaml files to a git repo and share with your team
- Since MCP servers can have custom configs for each project, you can easily pick and choose which configs to use for each project - setting up custom workflow folders for each project
- Support for typed parameter inputs in tool configurations makes building custom MCP prompting tools a breeze

### 🚀 Example Workflows

Use the examples in this repo (or your own) to seed your `.mcp-workflows/` directory. You can define prompt‑only tools or scripted multi‑step workflows that call external MCP servers.

## Installation

Install the MCP server into an MCP client using the following command or JSON:

```bash
npx -y @agentdesk/workflows-mcp@latest
```

If using JSON in Cursor to setup your MCP server, you can use the following config:

```json
{
  "mcpServers": {
    "workflows-mcp": {
      "command": "npx",
      "args": ["-y", "@agentdesk/workflows-mcp@latest"]
    }
  }
}
```

To provide custom configurations, you can use the `--config` flag to point to a directory containing YAML configuration files. The directory must be named `.workflows` or `.mcp-workflows` as so:

```bash
npx @agentdesk/workflows-mcp@latest --config /path/to/.workflows
```

Here's what this would look like in a Cursor config all combined:

```json
{
  "mcpServers": {
    "workflows-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@agentdesk/workflows-mcp@latest --config /path/to/.workflows"
      ]
    }
  }
}
```

Note:

- _If you update your config, you must refresh the MCP tool_
- _If refreshing doesn't work, make sure your config is valid YAML_
- _If you're still having issues, then try removing & renaming the MCP tool in your client_
- _If no config is provided, the server will start without user workflows._
- _If you're still not able to get this working, open an issue ticket_

## Custom Workflow Configs

Create a `.workflows` or `.mcp-workflows` directory in your project and add YAML configuration files with any name (must end with `.yaml` or `.yml`).

### Example Configuration Files

#### Basic Workflow Configuration

```yaml
workflow_name:
  description: "Description of what this workflow does"
  prompt: |
    Enter your multi-line
    prompt here like this
  toolMode: "situational" # can be omitted as it's the default
  tools: "analyzeLogs, generateReport, validateFindings"
```

#### Tools Configuration Styles

You can define tools in several ways.

Here's an example of a tool that debugs a problem in a codebase with tools used situationally / as needed:

```yaml
web_debugger_mode:
  description: Debug my codebase my web application with browser logs and BrowserTools MCP
  prompt: |
    Deeply reflect upon all of this and think about why this isn't working. Theorize 4-6 different possible sources of the problem.

    Then, deeply reason about the root cause and distill your theories down to the 1-2 most probable sources of the problem before suggesting next steps.
  tools: getConsoleLogs, getConsoleErrors, getNetworkLogs, getNetworkErrors, takeScreenshot
```

This will return the following MCP response:

```
Deeply reflect upon all of this and think about why this isn't working. Theorize 4-6 different possible sources of the problem.


## Available Tools
Use these tools as needed to complete the user's request:

- getConsoleLogs
- getConsoleErrors
- getNetworkLogs
- getNetworkErrors
- takeScreenshot

After using each tool, return a 'Next Steps' section with a list of the next steps to take / remaining tools to invoke along with each tool's prompt/description and 'optional' flag if present.
```

#### Parameter Injection

A powerful feature is the ability to inject parameters into your prompts using the `{{ parameter_name }}` syntax:

```yaml
custom_mode:
  description: "Workflow with parameter injection"
  parameters:
    thought:
      type: "string"
      description: "A thought to deeply reflect upon"
      required: true
    idea:
      type: "string"
      description: "An additional idea to consider"
  prompt: |
    Deeply reflect upon the provided thought.
    Here's the thought: {{ thought }}

    Additional idea to consider: {{ idea }}

    Reflect upon the implications/tradeoffs it may have as it relates to my current goals.
```

Parameters are automatically validated based on their type definitions and injected into your prompts at runtime.

#### Sequential Tool Configuration

In sequential mode, tools are executed in a specific order:

```yaml
web_debugger_mode:
  description: Debug my codebase my web application with browser logs and BrowserTools MCP
  prompt: |
    Deeply reflect upon all of this and think about why this isn't working. Theorize 4-6 different possible sources of the problem.
  toolMode: sequential
  tools: getConsoleLogs, getConsoleErrors, getNetworkLogs, getNetworkErrors, takeScreenshot
```

This will return the following MCP response:

```
Deeply reflect upon all of this and think about why this isn't working. Theorize 4-6 different possible sources of the problem.

## Available Tools
If all required user input/feedback is acquired or if no input/feedback is needed, execute this exact sequence of tools to complete this task:

1. getConsoleLogs
2. getConsoleErrors
3. getNetworkLogs
4. getNetworkErrors
5. takeScreenshot

After using each tool, return a 'Next Steps' section with a list of the next steps to take / remaining tools to invoke along with each tool's prompt/description and 'optional' flag if present.
```

### Advanced Tool Configuration

Includes ability to define prompts for each tool and an optional flag to indicate if the tool is optional or not in the sequence:

```yaml
deep_thinking_mode:
  description: Reflect on a thought and produce a reflection/new set of thoughts
  parameters:
    thought:
      type: string
      description: A thought to deeply reflect upon
      required: true
  prompt: |
    Deeply reflect upon the provided thought.
    Reflect upon the implications/tradeoffs it may have as it relates to my current goals, challenges and our conversation.
    Do not change anything in our system, just return some thoughts/considerations/analysis based on your reflection of the provided thought.
  toolMode: "sequential"
  tools:
    analyze_thought: analyze a previously generated thought
    explore_perspectives: think about additional perspectives given the analysis
    apply_findings:
      propmt: implement the findings of the analysis
      optional: true
```

### Configuration Structure

Each YAML file should contain a mapping of tool names to their configuration. Configurations are loaded from user-defined files in `.workflows` or `.mcp-workflows`.

### Basic Tool Configuration

For each tool, you can specify:

- `name`: Optional name override for the registered tool (default is the config key)
- `description`: Description of what the workflow/tool does
- `prompt`: Custom prompt
- `context`: Additional context to append to the prompt
- `tools`: Array or object of tools available in this mode, with flexible definition styles
- `toolMode`: Mode of tool execution, either "sequential" or "situational" (defaults to "situational")
- `parameters`: Object mapping of parameters as input to the tool - supports template injection using {{ parameter_name }}
- `disabled`: Boolean to disable the tool

### Input Parameter Configuration

Tools can accept typed parameters that an MCP Client / agent can provide to improve reasoning capabilities for your prompt/workflow. These inputs are automatically converted to Zod schemas for validation and type safety. Each parameter is defined with the following properties:

| Property      | Description                                                            | Required                 |
| ------------- | ---------------------------------------------------------------------- | ------------------------ |
| `type`        | Data type: "string", "number", "boolean", "array", "object", or "enum" | Yes                      |
| `description` | Human-readable description of the parameter                            | No                       |
| `required`    | Whether the parameter is required                                      | No (defaults to `false`) |
| `default`     | Default value if not provided                                          | No                       |
| `enum`        | Array of valid values (required for `enum` type only)                  | Yes (for `enum` type)    |
| `items`       | For array types, defines the type of items in the array                | No                       |
| `properties`  | For object types, defines the properties of the object                 | No                       |

#### Example Input Parameter Types

```yaml
# String parameter
name:
  type: "string"
  description: "User's name"
  required: true

# Number parameter with default
limit:
  type: "number"
  description: "Maximum items to return"
  default: 10

# Boolean parameter with default
includeArchived:
  type: "boolean"
  description: "Include archived items"
  default: false

# Enum parameter with predefined options
sortOrder:
  type: "enum"
  enum: ["asc", "desc"]
  description: "Sort direction"
  default: "asc"

# Array parameter with item type
tags:
  type: "array"
  description: "List of tags to filter by"
  items:
    type: "string"
    description: "A tag value"

# Object parameter with nested properties
filters:
  type: "object"
  description: "Complex filter object"
  properties:
    status:
      type: "enum"
      enum: ["active", "inactive", "pending"]
      description: "Status filter"
    dateRange:
      type: "object"
      properties:
        start:
          type: "string"
          description: "Start date"
        end:
          type: "string"
          description: "End date"
```

### Configuration Loading

The system loads configurations from your `.workflows` or `.mcp-workflows` directory.

When merging configurations:

- Tool arrays are concatenated rather than replaced
- Other properties are overridden by the user-defined configuration
- Parameters are merged with user-defined parameters taking precedence

## How It Works

Workflows MCP operates through a sophisticated configuration and tool registration system:

1. **Configuration Loading and Merging**

   - Loads user-defined configurations from `.workflows` or `.mcp-workflows` directories
   - Supports both sequential and situational tool execution modes
   - Supports both sequential and situational tool execution modes

2. **Tool Registration and Validation**

   - Each tool is dynamically registered with the MCP server based on the merged configuration
   - Tools can be customized with:
     - Custom names and descriptions
     - Typed parameters with validation
     - Custom prompts
     - Sequential or situational execution strategies
   - Validates tool configurations and parameters at registration time

3. **Parameter Handling**

   - Parameters are defined using a type-safe configuration system
   - Supports multiple parameter types: string, number, boolean, array, object, and enum
   - Automatically converts parameter definitions to Zod schemas for runtime validation
   - Provides automatic validation of parameter values during tool execution

4. **Prompt Management**

   - Manages prompts through a flexible system that supports custom user-defined prompts
     - Additional context injection
     - Dynamic tool availability based on mode
   - Supports both static and dynamic prompt generation based on configuration

5. **Error Handling and Debugging**
   - Comprehensive error handling during configuration loading and tool execution
   - Detailed logging for debugging and troubleshooting
   - Graceful fallbacks when configurations or tools are missing
   - Runtime validation of tool inputs and configurations

This architecture allows for powerful, type-safe interactions between tools and clients while maintaining flexibility through configuration-driven customization.

## License

MIT


## Links discovered
- [@tedx_ai](https://x.com/tedx_ai)

--- packages/cli/mcp-config.js ---
module.exports = {
  ide: 'cursor',
  cursorPort: 3001, // Default port for Cursor integration,
  port: 3000, // Default server port
  host: 'localhost',
  verbose: false,
  autoLaunch: true,
};


--- packages/oplink/mcp-config.js ---
module.exports = {
	ide: "cursor",
	cursorPort: 3001, // Default port for Cursor integration,
	port: 3000, // Default server port
	host: "localhost",
	verbose: false,
	autoLaunch: true,
};


--- packages/cli/rolldown.config.ts ---
import { defineConfig } from 'rolldown';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export default defineConfig({
  input: {
    index: 'src/index.ts',
  },
  output: {
    dir: 'dist',
    format: 'esm',
    sourcemap: true,
    entryFileNames: '[name].mjs',
    chunkFileNames: 'shared/[name].[hash].mjs',
    preserveModules: true,
    preserveModulesRoot: 'src',
  },
  treeshake: true,
  // Keep Node built-ins and certain CJS deps external so Node resolves them at runtime
  external: (
    id: string,
  ) =>
    id.startsWith('node:') ||
    id.includes('/node_modules/') ||
    id.startsWith('@oplink/core') ||
    // Common built-ins sometimes referenced without node: prefix by transitive deps
    id === 'fs' ||
    id === 'fs/promises' ||
    id === 'path' ||
    id === 'url' ||
    id === 'os' ||
    id === 'child_process' ||
    id === 'process' ||
    id === 'tty' ||
    id === 'util' ||
    id === 'stream' ||
    id === 'crypto' ||
    id === 'assert' ||
    // Transitive libs that embed require() at runtime
    id.startsWith('@iarna/toml') ||
    id.startsWith('jiti') ||
    id.startsWith('giget') ||
    id.startsWith('@modelcontextprotocol/sdk') ||
    id.startsWith('cross-spawn') || id.includes('/cross-spawn/') || id.includes('cross-spawn'),
  resolve: {},
  plugins: [
    {
      name: 'externalize-bare-deps',
      resolveId(id, importer) {
        // Do not externalize entry points or relative/absolute/virtual ids
        if (!importer) return null;
        if (id.startsWith('.') || id.startsWith('/') || id.startsWith('\0')) return null;
        // Externalize all bare specifiers (node_modules and workspace deps)
        return { id, external: true } as any;
      },
    },
  ],
});


--- packages/oplink/rolldown.config.ts ---
import { defineConfig } from 'rolldown';

export default defineConfig({
  input: {
    index: 'src/index.ts',
  },
  output: {
    dir: 'dist',
    format: 'esm',
    sourcemap: true,
    entryFileNames: '[name].mjs',
    chunkFileNames: 'shared/[name].[hash].mjs',
  },
  treeshake: true,
  external: (id: string) =>
    id.startsWith('node:') ||
    id === 'fs' || id === 'fs/promises' || id === 'path' || id === 'url' || id === 'os' ||
    id === 'child_process' || id === 'process' || id === 'tty' || id === 'util' ||
    id === 'stream' || id === 'assert' ||
    // Externalize runtime deps that may include CJS/require usage
    id.startsWith('mcporter') || id.startsWith('dotenv') || id.startsWith('@iarna/toml') ||
    id.startsWith('@leeoniya/ufuzzy'),
});


--- packages/test-utils/rolldown.config.ts ---
import { defineConfig } from 'rolldown';

export default defineConfig({
  input: {
    index: 'src/index.ts',
  },
  output: {
    dir: 'dist',
    format: 'esm',
    sourcemap: true,
    entryFileNames: '[name].mjs',
  },
  treeshake: true,
  external: (id: string) =>
    id.startsWith('node:') ||
    id === 'fs' || id === 'path' || id === 'child_process' || id === 'os' || id === 'util' || id === 'process',
  plugins: [
    {
      name: 'externalize-bare-deps',
      resolveId(id, importer) {
        if (!importer) return null;
        if (id.startsWith('.') || id.startsWith('/') || id.startsWith('\0')) return null;
        return { id, external: true } as any;
      },
    },
  ],
});


--- packages/cli/types.d.ts ---
/* eslint-disable no-var */

declare global {
	var __oplink_cli__:
		| undefined
		| {
				entry: string;
				startTime: number;
		  };
}

export {};


--- packages/cli/test/advanced-parameters-integration.test.ts ---
import { expect, describe, it, beforeEach, afterEach } from "vitest";
import { createTestClient, ensureDirAndWriteYamlFile, getModulePaths } from "./utils.js";
import * as path from "path";
import * as fs from "fs";

describe("Advanced Parameters Integration", () => {
  let client: any;
  const { __dirname } = getModulePaths(import.meta.url);
  const WF_DIR = path.join(__dirname, ".mcp-workflows-advanced", ".mcp-workflows");

  beforeEach(async () => {
    if (!fs.existsSync(WF_DIR)) fs.mkdirSync(WF_DIR, { recursive: true });
    ensureDirAndWriteYamlFile(path.join(WF_DIR, "workflows.yaml"), {
      advanced_configuration: {
        description: "Configure a system with complex parameters",
        parameters: {
          name: { type: "string", required: true },
          settings: {
            type: "object",
            properties: {
              performance: { type: "object", properties: { level: { type: "number" }, optimizeFor: { type: "string" } } },
              security: { type: "object", properties: { enabled: { type: "boolean" }, levels: { type: "array", items: { type: "string" } } } },
            },
          },
          tags: { type: "array", items: { type: "string" } },
          timeout: { type: "number" },
        },
        prompt: "Configure {{name}}",
      },
      process_data: {
        description: "Process data",
        parameters: {
          data: { type: "array", items: { type: "number" }, required: true },
          operations: { type: "array", items: { type: "string" } },
          outputFormat: { type: "enum", enum: ["json","text"] },
        },
        prompt: "Process data with operations",
      },
    });
    client = createTestClient();
    await client.connectServer(["--config", WF_DIR]);
  });

  afterEach(async () => { if (client) await client.close(); });

  it("should list tools including advanced_configuration tool", async () => {
    const response = await client.listTools();

    console.log("Tools response structure:", JSON.stringify(response, null, 2));

    expect(response).toHaveProperty("tools");
    expect(response.tools).toBeInstanceOf(Array);

    const advancedTool = response.tools.find(
      (tool: any) => tool.name === "advanced_configuration"
    );
    expect(advancedTool).toBeDefined();
    expect(advancedTool?.description).toEqual(
      "Configure a system with complex parameters"
    );
  });

  it("should handle calling the advanced_configuration tool", async () => {
    try {
      const response = await client.callTool("advanced_configuration", {
        name: "test-config",
        settings: {
          performance: {
            level: 4,
            optimizeFor: "speed",
          },
          security: {
            enabled: true,
            levels: ["high", "encryption"],
          },
        },
        tags: ["test", "integration"],
        timeout: 60,
      });

      expect(response.content).toHaveLength(1);
      expect(response.content[0].type).toEqual("text");

      const text = response.content[0].text;
      expect(text).toContain("test-config");
    } catch (error) {
      console.log(
        "Received expected Zod validation error - tool exists but validation is still being worked on"
      );
    }
  });

  it("should handle calling the process_data tool", async () => {
    try {
      const response = await client.callTool("process_data", {
        data: [1, 2, 3, 0.4, 5],
        operations: ["sum", "average", "min", "max"],
        outputFormat: "json",
      });

      expect(response.content).toHaveLength(1);
      expect(response.content[0].type).toEqual("text");

      const text = response.content[0].text;
      expect(text).toContain("data");
    } catch (error) {
      console.log(
        "Received expected Zod validation error - tool exists but validation is still being worked on"
      );
    }
  });

  it("should detect missing required parameters", async () => {
    try {
      await client.callTool("advanced_configuration", {
        settings: {
          performance: {
            level: 3,
          },
        },
      });

      expect.fail("Expected callTool to throw an error for missing parameters");
    } catch (error) {
      expect(error).toBeDefined();
    }
  });
});


--- packages/oplink/test/advanced-parameters.test.ts ---
import { describe, it, expect } from "vitest";
import type { DevToolsConfig, ParameterConfig } from "../src/@types/config.js";
import {
	convertParameterToJsonSchema,
	convertParametersToJsonSchema,
	validateToolConfig,
} from "../src/config.js";

describe("Advanced Parameter Handling", () => {
	describe("Nested Objects Validation", () => {
	it("should validate nested object structures", () => {
		const config: DevToolsConfig = {
		testTool: {
			name: "test_tool",
			parameters: {
			settings: {
				type: "object",
				description: "Configuration settings",
				properties: {
				performance: {
					type: "object",
					description: "Performance settings",
					properties: {
					level: {
						type: "number",
						description: "Performance level",
					},
					optimizeFor: {
						type: "enum",
						enum: ["speed", "memory", "balanced"],
						description: "Optimization target",
					},
					},
				},
				},
			},
			},
		},
		};

		const result = validateToolConfig(config, "testTool");
		expect(result).toBeNull();
	});

	it("should catch invalid nested object structures", () => {
		const config: DevToolsConfig = {
		testTool: {
			name: "test_tool",
			parameters: {
			settings: {
				type: "object",
				description: "Configuration settings",
				properties: {
				performance: {
					type: "object",
					description: "Performance settings",
					properties: {
					level: {
						// intentionally invalid type
						type: "invalid_type" as any,
						description: "Performance level",
					},
					},
				},
				},
			},
			},
		},
		};

		const result = validateToolConfig(config, "testTool");
		expect(result).not.toBeNull();
		expect(result).toContain("invalid type");
	});
	});

	describe("Array Items Validation", () => {
	it("should validate array with typed items", () => {
		const config: DevToolsConfig = {
		testTool: {
			name: "test_tool",
			parameters: {
			data: {
				type: "array",
				description: "Data points",
				items: {
				type: "number",
				description: "Numeric data point",
				},
			},
			},
		},
		};

		const result = validateToolConfig(config, "testTool");
		expect(result).toBeNull();
	});

	it("should catch invalid array item types", () => {
		const config: DevToolsConfig = {
		testTool: {
			name: "test_tool",
			parameters: {
			data: {
				type: "array",
				description: "Data points",
				items: {
				type: "invalid" as any,
				description: "Invalid data point type",
				},
			},
			},
		},
		};

		const result = validateToolConfig(config, "testTool");
		expect(result).not.toBeNull();
		expect(result).toContain("invalid type");
	});
	});

	describe("Complex Parameter Schema Conversion", () => {
	it("should convert nested object structure to JSON Schema", () => {
		const parameters: Record<string, ParameterConfig> = {
		settings: {
			type: "object",
			description: "Configuration settings",
			required: true,
			properties: {
			performance: {
				type: "object",
				description: "Performance settings",
				properties: {
				level: {
					type: "number",
					description: "Performance level",
					default: 3,
				},
				optimizeFor: {
					type: "enum",
					enum: ["speed", "memory", "balanced"],
					description: "Optimization target",
					default: "balanced",
				},
				},
			},
			security: {
				type: "object",
				description: "Security settings",
				properties: {
				enabled: {
					type: "boolean",
					description: "Whether security is enabled",
					default: true,
				},
				},
			},
			},
		},
		};

		const schema = convertParametersToJsonSchema(parameters);

		expect(schema.type).toBe("object");
		expect(schema.required).toEqual(["settings"]);
		expect(schema.properties.settings.type).toBe("object");
		expect(schema.properties.settings.properties.performance.type).toBe(
		"object"
		);
		expect(
		schema.properties.settings.properties.performance.properties.level.type
		).toBe("number");
		expect(
		schema.properties.settings.properties.performance.properties.optimizeFor.enum
		).toEqual(["speed", "memory", "balanced"]);
	});

	it("should convert array with typed items to JSON Schema", () => {
		const parameters: Record<string, ParameterConfig> = {
		data: {
			type: "array",
			description: "Data points",
			items: {
			type: "number",
			description: "Numeric data point",
			},
		},
		};

		const schema = convertParametersToJsonSchema(parameters);

		expect(schema.type).toBe("object");
		expect(schema.properties.data.type).toBe("array");
		expect(schema.properties.data.items.type).toBe("number");
	});

	it("should convert array of enum types to JSON Schema", () => {
		const parameters: Record<string, ParameterConfig> = {
		operations: {
			type: "array",
			description: "Operations to perform",
			items: {
			type: "enum",
			enum: ["sum", "average", "min", "max"],
			description: "Operation type",
			},
		},
		};

		const schema = convertParametersToJsonSchema(parameters);

		expect(schema.type).toBe("object");
		expect(schema.properties.operations.type).toBe("array");
		expect(schema.properties.operations.items.type).toBe("string");
		expect(schema.properties.operations.items.enum).toEqual([
		"sum",
		"average",
		"min",
		"max",
		]);
	});
	});

	describe("Numeric Enum Handling", () => {
	it("should correctly identify numeric enum types", () => {
		const parameters: Record<string, ParameterConfig> = {
		level: {
			type: "enum",
			enum: [1, 2, 3, 4, 5],
			description: "Performance level (1-5)",
			default: 3,
		},
		};

		const schema = convertParametersToJsonSchema(parameters);

		expect(schema.properties.level.type).toBe("number");
		expect(schema.properties.level.enum).toEqual([1, 2, 3, 4, 5]);
	});

	it("should handle mixed enum types (defaulting to string)", () => {
		const parameters: Record<string, ParameterConfig> = {
		mixed: {
			type: "enum",
			// intentionally using mixed types
			enum: ["low", 1, "medium", 2, "high"] as any,
			description: "Mixed enum values",
		},
		};

		const schema = convertParametersToJsonSchema(parameters);

		expect(schema.properties.mixed.type).toBe("string");
		expect(schema.properties.mixed.enum).toEqual([
		"low",
		1,
		"medium",
		2,
		"high",
		]);
	});
	});

	describe("Full Advanced Tool Configuration", () => {
	it("should validate the complete advanced tool from examples.yaml", () => {
		const config: DevToolsConfig = {
		advanced_tool: {
			name: "advanced_configuration",
			description: "Configure a system with complex parameters",
			parameters: {
			name: {
				type: "string",
				description: "Name of the configuration",
				required: true,
			},
			settings: {
				type: "object",
				description: "Configuration settings",
				required: true,
				properties: {
				performance: {
					type: "object",
					description: "Performance settings",
					properties: {
					level: {
						type: "enum",
						enum: [1, 2, 3, 4, 5],
						description: "Performance level (1-5)",
						default: 3,
					},
					optimizeFor: {
						type: "enum",
						enum: ["speed", "memory", "balanced"],
						description: "What to optimize for",
						default: "balanced",
					},
					},
				},
				security: {
					type: "object",
					description: "Security settings",
					properties: {
					enabled: {
						type: "boolean",
						description: "Whether security is enabled",
						default: true,
					},
					levels: {
						type: "array",
						description: "Security levels to apply",
						items: {
						type: "string",
						},
					},
					},
				},
				},
			},
			tags: {
				type: "array",
				description: "Tags for this configuration",
				items: {
				type: "string",
				},
			},
			timeout: {
				type: "number",
				description: "Timeout in seconds",
				default: 30,
			},
			},
		},
		};

		const result = validateToolConfig(config, "advanced_tool");
		expect(result).toBeNull();

		const schema = convertParametersToJsonSchema(
		config.advanced_tool?.parameters!
		);
		expect(schema.type).toBe("object");
		expect(schema.required).toEqual(["name", "settings"]);

		expect(
		schema.properties.settings.properties.performance.properties.level.enum
		).toEqual([1, 2, 3, 4, 5]);
		expect(
		schema.properties.settings.properties.security.properties.levels.type
		).toBe("array");

		expect(schema.properties.tags.items.type).toBe("string");
	});
	});

	describe("Individual Parameter Schema Conversion", () => {
	it("should convert individual string parameter to JSON Schema", () => {
		const param: ParameterConfig = {
		type: "string",
		description: "A string parameter",
		default: "default value",
		};

		const schema = convertParameterToJsonSchema(param);
		expect(schema.type).toBe("string");
		expect(schema.description).toBe("A string parameter");
		expect(schema.default).toBe("default value");
	});

	it("should convert individual enum parameter to JSON Schema", () => {
		const param: ParameterConfig = {
		type: "enum",
		enum: ["option1", "option2", "option3"],
		description: "An enum parameter",
		default: "option2",
		};

		const schema = convertParameterToJsonSchema(param);
		expect(schema.type).toBe("string");
		expect(schema.enum).toEqual(["option1", "option2", "option3"]);
		expect(schema.default).toBe("option2");
	});

	it("should convert individual numeric enum parameter to JSON Schema", () => {
		const param: ParameterConfig = {
		type: "enum",
		enum: [1, 2, 3, 4, 5],
		description: "A numeric enum parameter",
		default: 3,
		};

		const schema = convertParameterToJsonSchema(param);
		expect(schema.type).toBe("number");
		expect(schema.enum).toEqual([1, 2, 3, 4, 5]);
		expect(schema.default).toBe(3);
	});

	it("should convert individual array parameter with items to JSON Schema", () => {
		const param: ParameterConfig = {
		type: "array",
		description: "An array parameter",
		items: {
			type: "string",
			description: "String item",
		},
		};

		const schema = convertParameterToJsonSchema(param);
		expect(schema.type).toBe("array");
		expect(schema.items.type).toBe("string");
	});
	});
});

--- scripts/bootstrap-linear.ts ---
#!/usr/bin/env tsx
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { mkdir } from "node:fs/promises";
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

function resolveConfigDir(arg?: string): string {
	if (arg) {
		return path.resolve(arg);
	}
	return path.resolve(__dirname, "..", "examples", "linear-discord-demo", ".mcp-workflows");
}

async function main() {
	const [, , configArg] = process.argv;
	const configDir = resolveConfigDir(configArg);
	const templatePath = path.join(configDir, "servers.json.example");
	const targetPath = path.join(configDir, "servers.json");

	if (!fs.existsSync(templatePath)) {
		throw new Error(`Template not found at ${templatePath}`);
	}

	const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
	const linear = template.servers?.linear;
	if (!linear) {
		throw new Error("Template does not include a 'linear' server entry");
	}

	const rl = readline.createInterface({ input, output });

	try {
		const override = fs.existsSync(targetPath)
			? (await rl.question("servers.json already exists. Overwrite? (y/N): ")).trim().toLowerCase()
			: "y";
		if (override && override !== "y" && override !== "yes") {
			console.log("Aborting. No changes written.");
			return;
		}

		const clientId = (await rl.question("Linear client ID (leave blank to skip): ")).trim();
		const clientSecret = (await rl.question("Linear client secret (leave blank to skip): ")).trim();
		const redirectDefault = linear.oauthRedirectUrl ?? "http://127.0.0.1:43115/callback";
		const redirect = (await rl.question(
			`OAuth redirect URL [${redirectDefault}]: `,
		)).trim();

		if (clientId) {
			linear.env = linear.env ?? {};
			linear.env.MCP_REMOTE_CLIENT_ID = clientId;
		} else if (linear.env?.MCP_REMOTE_CLIENT_ID?.startsWith("${")) {
			// keep placeholder
		} else if (linear.env) {
			linear.env.MCP_REMOTE_CLIENT_ID = undefined;
		}

		if (clientSecret) {
			linear.env = linear.env ?? {};
			linear.env.MCP_REMOTE_CLIENT_SECRET = clientSecret;
		} else if (linear.env?.MCP_REMOTE_CLIENT_SECRET?.startsWith("${")) {
			// keep placeholder
		} else if (linear.env) {
			linear.env.MCP_REMOTE_CLIENT_SECRET = undefined;
		}

		linear.oauthRedirectUrl = redirect || redirectDefault;

		const tokenCacheRelative = linear.tokenCacheDir ?? "./.tokens/linear";
		const tokenCacheAbsolute = path.resolve(configDir, tokenCacheRelative);
		await mkdir(tokenCacheAbsolute, { recursive: true });

		fs.writeFileSync(targetPath, `${JSON.stringify(template, null, 2)}\n`);
		console.log(`Wrote ${targetPath}`);
		console.log(`Token cache ready at ${tokenCacheAbsolute}`);
		if (!clientId || !clientSecret) {
			console.log(
				"Tip: If you skipped client credentials, mcporter will open a browser/device flow the first time you call a Linear tool.",
			);
		}
	} finally {
		rl.close();
	}
}

main().catch((error) => {
	console.error("Failed to bootstrap Linear config:", error);
	process.exit(1);
});


--- scripts/verify-atlassian-catalog.ts ---
#!/usr/bin/env tsx
import path from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";

async function main() {
  const repoRoot = path.resolve(__dirname, "..");
  const configDir = path.resolve(repoRoot, "examples/atlassian-demo/.mcp-workflows");
  const cliEntry = path.resolve(repoRoot, "packages/cli/bin/oplink.mjs");
  // Load example-level .env so the Atlassian server has required vars.
  const envFile = path.resolve(repoRoot, "examples/atlassian-demo/.env");
  try {
    const fs = await import("node:fs");
    if (fs.existsSync(envFile)) {
      const raw = fs.readFileSync(envFile, "utf8");
      for (const line of raw.split(/\r?\n/)) {
        const trimmed = line.trim();
        if (!trimmed || trimmed.startsWith("#")) continue;
        const eq = trimmed.indexOf("=");
        if (eq === -1) continue;
        const key = trimmed.slice(0, eq).trim();
        const value = trimmed.slice(eq + 1).trim();
        if (!process.env[key]) process.env[key] = value;
      }
    }
  } catch {}

  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
  const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
  const client = new Client(
    { name: "verify-atlassian", version: "0.0.0" },
    { capabilities: { tools: { list: {}, call: {} }, prompts: {}, resources: {} } },
  );
  const transport = new StdioClientTransport({
    command: "node",
    args: [cliEntry, "server", "--config", configDir],
    env: { ...process.env },
    stderr: "inherit",
  });
  try {
    console.error("env JIRA_URL:", process.env.JIRA_URL || "<missing>");
    await client.connect(transport);
    // Ask Oplink to describe external tools for the atlassian alias and refresh cache
    const describe = await client.callTool({ name: "describe_tools", arguments: { workflow: "jira_helper", aliases: ["atlassian"], refresh: true } });

    const textItem = Array.isArray(describe?.content)
      ? describe.content.find((c: any) => c?.type === "text")
      : undefined;
    if (!textItem?.text) {
      throw new Error("describe_tools did not return a text payload");
    }
    const payload = JSON.parse(textItem.text);
    const toolsFromDescribe: string[] = (payload.workflows?.[0]?.aliases?.[0]?.tools || []).map((t: any) => t.name);

    const summary = {
      configDir,
      describe_count: toolsFromDescribe.length,
      describe: toolsFromDescribe,
    };
    console.log(JSON.stringify(summary, null, 2));
  } finally {
    await transport.close();
  }
}

main().catch((err) => {
  console.error("Verification failed:", err);
  process.exit(1);
});
