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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
*.log
.DS_Store
dist/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Taskade

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
103 changes: 102 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,104 @@
# @taskade/cli

Thin CLI for Taskade MCP plug-and-play.
Thin CLI for Taskade MCP plug-and-play. One command to connect Cursor or Claude to your Taskade workspace.

## Quick Start

```bash
# 1. Get your token at https://taskade.com/settings/api (looks like tskdp_...)

# 2. Connect Cursor
npx @taskade/cli plug cursor

# 3. Connect Claude
npx @taskade/cli plug claude

# 4. Verify your token works
npx @taskade/cli whoami
```

## What This Does

This CLI prints the MCP server config block you need to paste into your AI agent client (Cursor, Claude Desktop, Claude Code). It also verifies your Personal Access Token (PAT) works against the Taskade API.

**The token is the product.** You do not need this CLI to use Taskade MCP - you can copy the config block manually from [https://taskade.com/settings/api](https://taskade.com/settings/api). This CLI just makes it one command instead of five clicks.

## Commands

### `taskade plug [cursor|claude]`

Prints the MCP server config block.

```bash
npx @taskade/cli plug cursor # Cursor format with instructions
npx @taskade/cli plug claude # Claude Desktop/Code format with instructions
npx @taskade/cli plug # Raw JSON, no client wrapper
```

Output (raw JSON):

```json
{
"mcpServers": {
"taskade": {
"url": "https://taskade.com/mcp",
"headers": {
"Authorization": "Bearer tskdp_..."
}
}
}
}
```

### `taskade whoami`

Verifies your PAT works against the Taskade API.

```bash
npx @taskade/cli whoami
```

Output:

```
Token works.
User: John Doe
Email: john@example.com
Plan: pro

MCP server URL: https://taskade.com/mcp
```

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `TASKADE_TOKEN` | (prompt) | Your PAT (`tskdp_...`). Get one at [https://taskade.com/settings/api](https://taskade.com/settings/api) |
| `TASKADE_API_URL` | `https://www.taskade.com` | API base URL (for self-hosted) |

## Requirements

- Node.js 18+ (uses built-in `fetch`)

## Hosted MCP vs stdio

| | Hosted MCP | stdio (npm) |
|---|-----------|-------------|
| URL | `https://taskade.com/mcp` | `npx @taskade/mcp-server` |
| Auth | PAT (`tskdp_...`) | PAT (`tskdp_...`) |
| Tools | 48 (44 Phase A + 4 native) | 14 (read-only subset) |
| Plan | Starter+ | Any |
| Best for | Cursor, Claude Desktop, Claude Code | Local stdio wrapper |

**Use hosted MCP.** It has 3x more tools (including writes) and is maintained. The stdio wrapper is a local fallback only.

## Related

- [Hosted MCP](https://taskade.com/mcp) - the server this CLI configures
- [API v2 docs](https://www.taskade.com/api/documentation/v2) - the REST API the MCP wraps
- [taskade/mcp](https://github.com/taskade/mcp) - the MCP server repo
- [taskade/docs](https://github.com/taskade/docs) - developer documentation

## License

MIT
215 changes: 215 additions & 0 deletions bin/cli.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#!/usr/bin/env node

/**
* @taskade/cli - Thin CLI for Taskade MCP plug-and-play.
*
* Commands:
* taskade plug [cursor|claude] Print the MCP server config block for your client.
* taskade whoami Verify your PAT works against the Taskade API.
* taskade help Show this help.
*
* Usage:
* npx @taskade/cli plug cursor # copy-paste into Cursor's MCP config
* npx @taskade/cli whoami # paste your tskdp_ token, verify it works
*
* Environment:
* TASKADE_TOKEN Your Personal Access Token (tskdp_...). If unset, you will be
* prompted to paste it. Get one at https://taskade.com/settings/api
* TASKADE_API_URL API base URL (default: https://www.taskade.com)
*/

const DEFAULT_API_URL = "https://www.taskade.com";
const MCP_SERVER_URL = "https://taskade.com/mcp";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Read a line from stdin (used for token prompt). */
function readLine() {
return new Promise((resolve) => {
process.stdin.resume();
process.stdin.setEncoding("utf8");
if (process.stdin.isTTY) {
// Interactive: wait for a single line (Enter key)
process.stdin.once("data", (data) => {
process.stdin.pause();
resolve(data.trim());
});
} else {
// Non-interactive (pipe): read all of stdin, resolve on end
let data = "";
process.stdin.on("data", (chunk) => {
data += chunk;
});
process.stdin.on("end", () => {
process.stdin.pause();
resolve(data.trim());
});
}
});
}

/** Get the token from env or prompt. */
async function getToken() {
if (process.env.TASKADE_TOKEN) return process.env.TASKADE_TOKEN;
process.stderr.write("Paste your Taskade PAT (tskdp_...): ");
return readLine();
}

// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------

/** `taskade plug [cursor|claude]` - print the MCP config block. */
async function cmdPlug(client) {
const token = await getToken();
if (!token) {
process.stderr.write("No token provided. Get one at https://taskade.com/settings/api\n");
process.exit(1);
}

const config = {
mcpServers: {
taskade: {
url: MCP_SERVER_URL,
headers: {
Authorization: `Bearer ${token}`,
},
},
},
};

const json = JSON.stringify(config, null, 2);

if (client === "cursor") {
// Cursor reads ~/.cursor/mcp.json or project .cursor/mcp.json
process.stdout.write(`# Add this to your Cursor MCP config\n`);
process.stdout.write(`# ~/.cursor/mcp.json or .cursor/mcp.json in your project\n\n`);
process.stdout.write(json);
process.stdout.write("\n");
} else if (client === "claude") {
// Claude Desktop / Claude Code reads claude_desktop_config.json
process.stdout.write(`# Add this to your Claude config\n`);
process.stdout.write(`# ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)\n`);
process.stdout.write(`# or .mcp.json in your project root (Claude Code)\n\n`);
process.stdout.write(json);
process.stdout.write("\n");
} else {
// No client specified - just print the raw JSON
process.stdout.write(json);
process.stdout.write("\n");
}
}

/** `taskade whoami` - verify the PAT works. */
async function cmdWhoami() {
const token = await getToken();
if (!token) {
process.stderr.write("No token provided. Get one at https://taskade.com/settings/api\n");
process.exit(1);
}

const apiUrl = process.env.TASKADE_API_URL || DEFAULT_API_URL;
const url = `${apiUrl}/api/v2/user`;

try {
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});

if (res.status === 401) {
process.stderr.write("Token is invalid or expired. Get a new one at https://taskade.com/settings/api\n");
process.exit(1);
}

if (res.status === 403) {
process.stderr.write("Token is valid but lacks access. Check your plan at https://taskade.com/settings/billing\n");
process.exit(1);
}

if (!res.ok) {
process.stderr.write(`API returned ${res.status} ${res.statusText}\n`);
process.exit(1);
}

const data = await res.json();
const name = data.full_name || data.name || data.email || "unknown";
const email = data.email || "";
process.stdout.write(`Token works.\n`);
process.stdout.write(` User: ${name}\n`);
if (email) process.stdout.write(` Email: ${email}\n`);
process.stdout.write(` Plan: ${data.plan || "unknown"}\n`);
process.stdout.write(`\nMCP server URL: ${MCP_SERVER_URL}\n`);
} catch (err) {
process.stderr.write(`Request failed: ${err.message}\n`);
process.exit(1);
}
}

/** `taskade help` - show usage. */
function cmdHelp() {
process.stdout.write(`
@taskade/cli - Thin CLI for Taskade MCP plug-and-play

USAGE
taskade <command> [options]

COMMANDS
plug [cursor|claude] Print the MCP server config block for your client
whoami Verify your PAT works against the Taskade API
help Show this help message

EXAMPLES
npx @taskade/cli plug cursor # config block for Cursor
npx @taskade/cli plug claude # config block for Claude Desktop/Code
npx @taskade/cli plug # raw JSON, no client wrapper
npx @taskade/cli whoami # verify your token

ENVIRONMENT
TASKADE_TOKEN Your PAT (tskdp_...). Get one at https://taskade.com/settings/api
TASKADE_API_URL API base URL (default: https://www.taskade.com)

LEARN MORE
Hosted MCP: https://taskade.com/mcp
API docs: https://www.taskade.com/api/documentation/v2
GitHub: https://github.com/taskade/cli
`);
}

// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------

async function main() {
const [cmd, ...args] = process.argv.slice(2);

switch (cmd) {
case "plug":
await cmdPlug(args[0]);
break;
case "whoami":
await cmdWhoami();
break;
case "help":
case "--help":
case "-h":
cmdHelp();
break;
case undefined:
cmdHelp();
break;
default:
process.stderr.write(`Unknown command: ${cmd}\n\n`);
cmdHelp();
process.exit(1);
}
}

main().catch((err) => {
process.stderr.write(`Error: ${err.message}\n`);
process.exit(1);
});
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@taskade/cli",
"version": "0.1.0",
"description": "Thin CLI for Taskade MCP plug-and-play. One command to connect Cursor/Claude to your Taskade workspace.",
"type": "module",
"bin": {
"taskade": "./bin/cli.mjs"
},
"files": [
"bin/"
],
"scripts": {
"test": "node --test test/*.test.mjs"
},
"keywords": [
"taskade",
"mcp",
"cli",
"cursor",
"claude",
"ai-agent"
],
"license": "MIT",
"homepage": "https://github.com/taskade/cli",
"repository": {
"type": "git",
"url": "git+https://github.com/taskade/cli.git"
},
"engines": {
"node": ">=18"
}
}
Loading