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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ pos-cli/
│ ├── pos-cli-lsp.js # Language Server Protocol server
│ ├── pos-cli-mcp.js # MCP server entry point
│ ├── pos-cli-mcp-config.js # Display MCP tool configuration
│ ├── pos-cli-ai.js # AI tools command group
│ ├── pos-cli-ai-init.js # Wizard: register MCP servers in AI tool config
│ ├── pos-cli-supervisor.js # platformos-mcp-supervisor wrapper (validate_code MCP server)
│ └── pos-cli-fetch-logs.js # Fetch logs as NDJSON (for scripting/MCP)
├── lib/ # Core business logic
│ ├── proxy.js # Gateway class - main API client
Expand All @@ -70,6 +73,7 @@ pos-cli/
│ ├── archive.js # Deployment archive creation
│ ├── push.js # Archive upload
│ ├── check.js # Liquid/JSON linter (platformos-check)
│ ├── ai.js # AI tool MCP config wizard (pos-cli ai init)
│ ├── templates.js # Mustache template processing
│ ├── modules.js # Module lifecycle management
│ ├── deploy/ # Deployment strategies
Expand Down
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,25 @@ Example:

pos-cli includes a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that exposes platformOS operations as tools for AI clients such as Claude, Cursor, and other MCP-compatible assistants. This lets AI agents deploy code, run GraphQL queries, manage data, execute migrations, and more — all directly against your platformOS instances.

#### Quick setup: pos-cli ai init

The fastest way to connect your AI tool is the one-step wizard:

pos-cli ai init

It asks which AI tool you use and registers both platformOS MCP servers — `platformos` (the pos-cli tools listed below) and `platformos-supervisor` (Liquid/GraphQL/YAML code validation via `validate_code`) — in that tool's project-scoped configuration:

| Tool | Configuration file | Key |
| ----------- | ------------------- | ------------ |
| Claude Code | `.mcp.json` | `mcpServers` |
| Cursor | `.cursor/mcp.json` | `mcpServers` |
| VS Code | `.vscode/mcp.json` | `servers` |
| Other | prints the JSON snippet for manual setup | — |

Run it from your project root. Existing configuration files are merged, never overwritten — other MCP servers and unrelated settings are preserved, and re-running the command is a no-op. To skip the prompt (e.g. in scripts), pass the tool directly:

pos-cli ai init --tool claude

#### Starting the MCP Server

pos-cli mcp
Expand All @@ -882,18 +901,31 @@ This starts both a **stdio transport** (for editor/AI integrations) and an **HTT

#### Configuring Claude Code

Add the following to your Claude Code settings (`.claude/settings.json`) to use pos-cli as an MCP server:
Run `pos-cli ai init --tool claude` to generate this automatically, or add the following to a `.mcp.json` file at your project root:

```json
{
"mcpServers": {
"platformos": {
"command": "pos-cli-mcp"
},
"platformos-supervisor": {
"command": "pos-cli-supervisor"
}
}
}
```

Both commands are installed globally with pos-cli (`npm install -g @platformos/pos-cli`).

#### Supervisor MCP server (pos-cli-supervisor)

pos-cli-supervisor [--project <dir>]

Starts the [platformOS MCP supervisor](https://github.com/Platform-OS/platformos-tools/tree/master/packages/platformos-mcp-supervisor) — a stdio-only MCP server exposing a single `validate_code` tool that lets AI agents validate platformOS Liquid, GraphQL, and YAML files *before* writing them. It returns structured errors, warnings, proposed fixes, and a `must_fix_before_write` gate.

The project directory is resolved from `--project`, then the `POS_SUPERVISOR_PROJECT_DIR` environment variable, then the current working directory (which is what MCP clients use when launched via `pos-cli ai init`). All logging goes to stderr — stdout is reserved for the MCP protocol.

#### Available Tools

The MCP server exposes 30+ tools across these categories:
Expand Down
21 changes: 21 additions & 0 deletions bin/pos-cli-ai-init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node
import { Option } from 'commander';
import { program } from '../lib/program.js';
import { init } from '../lib/ai.js';

program
.name('pos-cli ai init')
.description('register platformOS MCP servers (platformos, platformos-supervisor) in your AI tool configuration')
.addOption(
new Option('--tool <tool>', 'skip the interactive prompt and configure the given tool').choices([
'claude',
'cursor',
'vscode',
'other'
])
)
.action(async (options) => {
await init({ tool: options.tool });
});

program.parse(process.argv);
7 changes: 7 additions & 0 deletions bin/pos-cli-ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import { program } from '../lib/program.js';

program
.name('pos-cli ai')
.command('init', 'register platformOS MCP servers in your AI tool configuration')
.parse(process.argv);
31 changes: 31 additions & 0 deletions bin/pos-cli-supervisor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node

// stdout is reserved for MCP JSON-RPC - never write to it in this file.
import path from 'path';
import logger from '../lib/logger.js';

const loadSupervisor = async () => {
try {
return await import('@platformos/platformos-mcp-supervisor');
} catch {
await logger.Error(
'The @platformos/platformos-mcp-supervisor package is not installed.\n' +
'Install it with: npm install -g @platformos/pos-cli',
{ notify: false }
);
}
};

// same precedence as the upstream bin: --project > POS_SUPERVISOR_PROJECT_DIR > cwd
const args = process.argv.slice(2);
const projectFlagIndex = args.indexOf('--project');
const projectDir = path.resolve(
(projectFlagIndex !== -1 && args[projectFlagIndex + 1]) ||
args.find((arg) => arg.startsWith('--project='))?.slice('--project='.length) ||
process.env.POS_SUPERVISOR_PROJECT_DIR ||
process.cwd()
);

const supervisor = await loadSupervisor();
const startServer = supervisor.startServer ?? supervisor.default?.startServer;
await startServer({ projectDir });
1 change: 1 addition & 0 deletions bin/pos-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ program.showHelpAfterError();
program
.name('pos-cli')
.version(version, '-v, --version')
.command('ai', 'configure AI tools (register platformOS MCP servers)')
.command('archive', 'create an archive only (no deployment)')
.command('audit', 'check your code for deprecations, recommendations, errors')
.command('check', 'check Liquid code quality with platformos-check linter')
Expand Down
83 changes: 83 additions & 0 deletions lib/ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import fs from 'fs';
import path from 'path';
import logger from './logger.js';

const SERVERS = {
platformos: { command: 'pos-cli-mcp' },
'platformos-supervisor': { command: 'pos-cli-supervisor' }
};

const TOOLS = {
claude: { label: 'Claude Code', file: '.mcp.json', key: 'mcpServers', entry: (server) => ({ ...server }) },
cursor: { label: 'Cursor', file: '.cursor/mcp.json', key: 'mcpServers', entry: (server) => ({ ...server }) },
vscode: { label: 'VS Code', file: '.vscode/mcp.json', key: 'servers', entry: (server) => ({ type: 'stdio', ...server }) },
other: { label: 'Other' }
};

const promptForTool = async () => {
const { select } = await import('@inquirer/prompts');
try {
return await select({
message: 'Which AI tool do you use?',
choices: Object.entries(TOOLS).map(([value, tool]) => ({ name: tool.label, value }))
});
} catch (error) {
if (error.name === 'ExitPromptError') {
process.exit(0);
}
throw error;
}
};

const printManualSnippet = async () => {
await logger.Info('Add these stdio MCP servers to your AI tool configuration:', { hideTimestamp: true });
await logger.Log(JSON.stringify({ mcpServers: SERVERS }, null, 2));
};

const configureTool = async (toolId, rootPath) => {
const tool = TOOLS[toolId];
const configPath = path.join(rootPath, ...tool.file.split('/'));

let config = {};
if (fs.existsSync(configPath)) {
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (error) {
return logger.Error(
`${tool.file} exists but is not valid JSON: ${error.message}\n` +
'Fix or remove the file and run pos-cli ai init again.'
);
}
}

const servers = (config[tool.key] = config[tool.key] || {});
const added = [];
const updated = [];

for (const [name, server] of Object.entries(SERVERS)) {
const desired = tool.entry(server);
if (JSON.stringify(servers[name]) === JSON.stringify(desired)) continue;
(servers[name] ? updated : added).push(name);
servers[name] = desired;
}

if (added.length === 0 && updated.length === 0) {
return logger.Success(`${tool.label} is already configured in ${tool.file} - nothing to do.`);
}

fs.mkdirSync(path.dirname(configPath), { recursive: true });
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');

if (updated.length > 0) {
await logger.Info(`Updated existing entries in ${tool.file}: ${updated.join(', ')}`);
}
await logger.Success(`Registered MCP servers (${Object.keys(SERVERS).join(', ')}) for ${tool.label} in ${tool.file}`);
};

const init = async ({ tool, rootPath = process.cwd() } = {}) => {
const toolId = tool || (await promptForTool());
if (toolId === 'other') return printManualSnippet();
return configureTool(toolId, rootPath);
};

export { init, SERVERS, TOOLS };
Loading
Loading