@ompo-design/mcp-server 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,67 +1,106 @@
1
1
  # @ompo-design/mcp-server
2
2
 
3
- Local MCP server for applying Ompo visual edits to a codebase.
3
+ MCP server for applying Ompo visual edits to your codebase.
4
4
 
5
- ## Setup
5
+ ## Important: this is not a web connector
6
6
 
7
- ### Cursor
7
+ **Settings → Connectors → Add custom connector** only works for **remote URLs** (hosted servers).
8
8
 
9
- Add to `.cursor/mcp.json` in your project:
9
+ Ompo is a **local** MCP server. Install it via the steps below — not through the Connectors URL dialog.
10
10
 
11
- ```json
12
- {
13
- "mcpServers": {
14
- "ompo": {
15
- "command": "npx",
16
- "args": ["-y", "@ompo-design/mcp-server"]
17
- }
18
- }
19
- }
11
+ ---
12
+
13
+ ## Claude Desktop — install for ALL projects (recommended)
14
+
15
+ **Prerequisites:** [Node.js](https://nodejs.org) installed.
16
+
17
+ ### 1. Run one command in Terminal
18
+
19
+ ```bash
20
+ npx @ompo-design/mcp-server setup-global
20
21
  ```
21
22
 
22
- ### Claude Code
23
+ This adds Ompo to:
24
+ - Claude Desktop (`claude_desktop_config.json`)
25
+ - Claude Code user config (`~/.claude.json`)
26
+
27
+ Works across **every project**, not just one folder.
28
+
29
+ ### 2. Quit and reopen Claude Desktop
30
+
31
+ Fully quit with **Cmd+Q**, then reopen.
23
32
 
24
- Add to `.mcp.json` in your project:
33
+ ### 3. Verify
34
+
35
+ Open any **Code** project, then type:
36
+
37
+ ```text
38
+ /mcp
39
+ ```
40
+
41
+ **ompo** should show **4 tools**. Approve it if prompted.
42
+
43
+ ---
44
+
45
+ ## Manual install (Claude Desktop)
46
+
47
+ 1. Open **Claude Desktop → Settings → Developer → Edit Config**
48
+ 2. Add inside `mcpServers`:
25
49
 
26
50
  ```json
27
- {
28
- "mcpServers": {
29
- "ompo": {
30
- "type": "stdio",
31
- "command": "npx",
32
- "args": ["-y", "@ompo-design/mcp-server"]
33
- }
34
- }
51
+ "ompo": {
52
+ "command": "/usr/local/bin/npx",
53
+ "args": ["-y", "@ompo-design/mcp-server"],
54
+ "env": {}
35
55
  }
36
56
  ```
37
57
 
38
- Restart your IDE session after adding the config. In Claude Code, run `/mcp` to confirm `ompo` is connected with 4 tools.
58
+ 3. Save, quit Claude Desktop (Cmd+Q), reopen.
39
59
 
40
- ## Workflow
60
+ Use the full path to `npx` — the Desktop app often cannot find `npx` on its own.
41
61
 
42
- 1. Edit your localhost site in Ompo
43
- 2. Click **Send** and copy the prompt
44
- 3. Paste the prompt into Cursor or Claude Code
45
- 4. The agent calls:
46
- - `get_edit({ id: "ed_8K42P" })`
47
- - `apply_edit({ id: "ed_8K42P" })`
48
- 5. The agent applies only the listed property changes to source files
62
+ ---
49
63
 
50
- Ompo writes edit bundles to `.ompo/edits/` in your project. Open that project in your IDE so the MCP server can find them.
64
+ ## Per-project install (optional)
51
65
 
52
- ## Tools
66
+ From a project folder:
53
67
 
54
- - `list_edits` — list saved edit bundles in `.ompo/edits/`
55
- - `get_edit` — load a bundle by id
56
- - `explain_edit` — short summary of an edit
57
- - `apply_edit` — structured apply plan for the agent
68
+ ```bash
69
+ npx @ompo-design/mcp-server setup
70
+ ```
58
71
 
59
- ## Development
72
+ Creates `.mcp.json` in that project only.
73
+
74
+ ---
75
+
76
+ ## Check install
60
77
 
61
78
  ```bash
62
- npm install
63
- npm run build
64
- npm start
79
+ npx @ompo-design/mcp-server doctor
65
80
  ```
66
81
 
67
- For local testing before publish, point your MCP config at `node /absolute/path/to/mcp-server/dist/index.js`.
82
+ ---
83
+
84
+ ## Using Ompo
85
+
86
+ 1. Edit your site in **Ompo**
87
+ 2. Click **Send** (creates `.ompo/edits/` in your project)
88
+ 3. Open that **same project** in Claude Code
89
+ 4. Paste the Send prompt — Claude calls `get_edit` and `apply_edit`
90
+
91
+ ---
92
+
93
+ ## Cursor
94
+
95
+ `.cursor/mcp.json`:
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "ompo": {
101
+ "command": "npx",
102
+ "args": ["-y", "@ompo-design/mcp-server"]
103
+ }
104
+ }
105
+ }
106
+ ```
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runCli(argv: string[]): boolean;
package/dist/cli.js ADDED
@@ -0,0 +1,186 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join, resolve } from 'path';
5
+ import { editsStoreReady } from './edit-store.js';
6
+ import { getOmpoEditsStorePath } from './edits-path.js';
7
+ const PACKAGE_NAME = '@ompo-design/mcp-server';
8
+ const SERVER_NAME = 'ompo';
9
+ function resolveExecutable(name) {
10
+ try {
11
+ return execSync(`which ${name}`, { encoding: 'utf8' }).trim();
12
+ }
13
+ catch {
14
+ return name;
15
+ }
16
+ }
17
+ function ompoServerEntry() {
18
+ const npxPath = resolveExecutable('npx');
19
+ return {
20
+ type: 'stdio',
21
+ command: npxPath,
22
+ args: ['-y', PACKAGE_NAME],
23
+ env: {}
24
+ };
25
+ }
26
+ function ompoDesktopEntry() {
27
+ const npxPath = resolveExecutable('npx');
28
+ return {
29
+ command: npxPath,
30
+ args: ['-y', PACKAGE_NAME],
31
+ env: {}
32
+ };
33
+ }
34
+ const CLAUDE_CODE_CONFIG = {
35
+ mcpServers: {
36
+ [SERVER_NAME]: ompoServerEntry()
37
+ }
38
+ };
39
+ function readJsonFile(path) {
40
+ if (!existsSync(path))
41
+ return {};
42
+ try {
43
+ return JSON.parse(readFileSync(path, 'utf8'));
44
+ }
45
+ catch {
46
+ return {};
47
+ }
48
+ }
49
+ function writeJsonFile(path, data) {
50
+ const directory = join(path, '..');
51
+ if (!existsSync(directory))
52
+ mkdirSync(directory, { recursive: true });
53
+ writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
54
+ }
55
+ function mergeMcpServer(config, serverName, serverEntry) {
56
+ const mcpServers = typeof config.mcpServers === 'object' && config.mcpServers !== null
57
+ ? { ...config.mcpServers }
58
+ : {};
59
+ mcpServers[serverName] = serverEntry;
60
+ return {
61
+ ...config,
62
+ mcpServers
63
+ };
64
+ }
65
+ function claudeDesktopConfigPath() {
66
+ if (process.platform === 'darwin') {
67
+ return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
68
+ }
69
+ if (process.platform === 'win32' && process.env.APPDATA) {
70
+ return join(process.env.APPDATA, 'Claude', 'claude_desktop_config.json');
71
+ }
72
+ return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
73
+ }
74
+ function claudeCodeUserConfigPath() {
75
+ return join(homedir(), '.claude.json');
76
+ }
77
+ export function runCli(argv) {
78
+ const command = argv[0];
79
+ if (command === 'setup') {
80
+ runProjectSetup();
81
+ return true;
82
+ }
83
+ if (command === 'setup-global') {
84
+ runGlobalSetup();
85
+ return true;
86
+ }
87
+ if (command === 'doctor') {
88
+ runDoctor();
89
+ return true;
90
+ }
91
+ if (command === 'help' || command === '--help' || command === '-h') {
92
+ printHelp();
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+ function runProjectSetup() {
98
+ const projectRoot = resolve(process.cwd());
99
+ const configPath = join(projectRoot, '.mcp.json');
100
+ writeJsonFile(configPath, CLAUDE_CODE_CONFIG);
101
+ console.log(`Created ${configPath}`);
102
+ printProjectNextSteps(projectRoot);
103
+ }
104
+ function runGlobalSetup() {
105
+ const desktopPath = claudeDesktopConfigPath();
106
+ const claudeCodePath = claudeCodeUserConfigPath();
107
+ const npxPath = resolveExecutable('npx');
108
+ const desktopConfig = mergeMcpServer(readJsonFile(desktopPath), SERVER_NAME, ompoDesktopEntry());
109
+ const claudeCodeConfig = mergeMcpServer(readJsonFile(claudeCodePath), SERVER_NAME, ompoServerEntry());
110
+ writeJsonFile(desktopPath, desktopConfig);
111
+ writeJsonFile(claudeCodePath, claudeCodeConfig);
112
+ console.log('Installed Ompo MCP globally for Claude Desktop and Claude Code.');
113
+ console.log('');
114
+ console.log(` Claude Desktop: ${desktopPath}`);
115
+ console.log(` Claude Code: ${claudeCodePath}`);
116
+ console.log(` npx path: ${npxPath}`);
117
+ console.log('');
118
+ console.log('Next steps:');
119
+ console.log(' 1. Quit Claude Desktop completely (Cmd+Q)');
120
+ console.log(' 2. Reopen Claude Desktop');
121
+ console.log(' 3. Open any Code project and run /mcp');
122
+ console.log(' 4. Approve ompo if prompted');
123
+ console.log(' 5. In Ompo, click Send on an edit before using the tools');
124
+ console.log(` Edits are stored in ${getOmpoEditsStorePath()}`);
125
+ }
126
+ function runDoctor() {
127
+ const projectRoot = resolve(process.cwd());
128
+ const configPath = join(projectRoot, '.mcp.json');
129
+ const desktopPath = claudeDesktopConfigPath();
130
+ const claudeCodePath = claudeCodeUserConfigPath();
131
+ const storePath = getOmpoEditsStorePath();
132
+ console.log('Ompo MCP doctor');
133
+ console.log('');
134
+ console.log(`Current folder: ${projectRoot}`);
135
+ console.log(`Node: ${process.version}`);
136
+ console.log(`npx: ${resolveExecutable('npx')}`);
137
+ console.log(`Edits store: ${storePath}`);
138
+ console.log('');
139
+ for (const [label, path] of [
140
+ ['Project MCP config', configPath],
141
+ ['Claude Desktop', desktopPath],
142
+ ['Claude Code user', claudeCodePath]
143
+ ]) {
144
+ const config = readJsonFile(path);
145
+ const servers = typeof config.mcpServers === 'object' && config.mcpServers !== null
146
+ ? config.mcpServers
147
+ : {};
148
+ if (servers[SERVER_NAME]) {
149
+ console.log(`${label}: ompo configured (${path})`);
150
+ }
151
+ else if (existsSync(path)) {
152
+ console.log(`${label}: found, but ompo missing (${path})`);
153
+ }
154
+ else {
155
+ console.log(`${label}: missing (${path})`);
156
+ }
157
+ }
158
+ console.log('');
159
+ if (editsStoreReady()) {
160
+ console.log('Edits: ready (Send from Ompo to add more)');
161
+ }
162
+ else {
163
+ console.log('Edits: none yet (click Send in Ompo first)');
164
+ }
165
+ console.log('');
166
+ console.log('Global install: npx @ompo-design/mcp-server setup-global');
167
+ }
168
+ function printProjectNextSteps(projectRoot) {
169
+ console.log('');
170
+ console.log('Next steps:');
171
+ console.log(` 1. cd ${projectRoot}`);
172
+ console.log(' 2. Start Claude Code in this folder');
173
+ console.log(' 3. Run /mcp and approve ompo if prompted');
174
+ console.log(' 4. In Ompo, click Send on an edit');
175
+ console.log('');
176
+ console.log('For all projects, run: npx @ompo-design/mcp-server setup-global');
177
+ }
178
+ function printHelp() {
179
+ console.log(`Ompo MCP server (${PACKAGE_NAME})`);
180
+ console.log('');
181
+ console.log('Usage:');
182
+ console.log(` npx ${PACKAGE_NAME} Run MCP server (used by your IDE)`);
183
+ console.log(` npx ${PACKAGE_NAME} setup Create .mcp.json in current project`);
184
+ console.log(` npx ${PACKAGE_NAME} setup-global Install for all Claude projects`);
185
+ console.log(` npx ${PACKAGE_NAME} doctor Check install status`);
186
+ }
@@ -1,6 +1,6 @@
1
1
  import type { EditIndex, OmpoEditBundle } from './types.js';
2
- export declare function findProjectRoot(startDir?: string): string | null;
3
- export declare function listEdits(projectRoot: string): EditIndex['edits'];
4
- export declare function readEditBundle(projectRoot: string, editId: string): OmpoEditBundle;
5
- export declare function recordEditPull(projectRoot: string, editId: string): void;
6
- export declare function recordEditApplied(projectRoot: string, editId: string): void;
2
+ export declare function editsStoreReady(): boolean;
3
+ export declare function listEdits(): EditIndex['edits'];
4
+ export declare function readEditBundle(editId: string): OmpoEditBundle;
5
+ export declare function recordEditPull(editId: string): void;
6
+ export declare function recordEditApplied(editId: string): void;
@@ -1,51 +1,44 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'fs';
2
- import { dirname, join, resolve } from 'path';
3
- import { OMPo_EDITS_DIR } from './types.js';
4
- export function findProjectRoot(startDir = process.cwd()) {
5
- let current = resolve(startDir);
6
- while (true) {
7
- const editsDirectory = join(current, OMPo_EDITS_DIR);
8
- if (existsSync(join(editsDirectory, 'index.json'))) {
9
- return current;
10
- }
11
- const parent = dirname(current);
12
- if (parent === current)
13
- return null;
14
- current = parent;
15
- }
2
+ import { join } from 'path';
3
+ import { getOmpoEditsStorePath } from './edits-path.js';
4
+ function editsDirectory() {
5
+ return getOmpoEditsStorePath();
6
+ }
7
+ export function editsStoreReady() {
8
+ return existsSync(join(editsDirectory(), 'index.json'));
16
9
  }
17
- function readIndex(projectRoot) {
18
- const indexPath = join(projectRoot, OMPo_EDITS_DIR, 'index.json');
10
+ function readIndex() {
11
+ const indexPath = join(editsDirectory(), 'index.json');
19
12
  return JSON.parse(readFileSync(indexPath, 'utf8'));
20
13
  }
21
- function writeIndex(projectRoot, index) {
22
- const indexPath = join(projectRoot, OMPo_EDITS_DIR, 'index.json');
14
+ function writeIndex(index) {
15
+ const indexPath = join(editsDirectory(), 'index.json');
23
16
  writeFileSync(indexPath, `${JSON.stringify(index, null, 2)}\n`, 'utf8');
24
17
  }
25
- export function listEdits(projectRoot) {
26
- return readIndex(projectRoot).edits;
18
+ export function listEdits() {
19
+ return readIndex().edits;
27
20
  }
28
- export function readEditBundle(projectRoot, editId) {
29
- const bundlePath = join(projectRoot, OMPo_EDITS_DIR, `${editId}.json`);
21
+ export function readEditBundle(editId) {
22
+ const bundlePath = join(editsDirectory(), `${editId}.json`);
30
23
  if (!existsSync(bundlePath)) {
31
24
  throw new Error(`Edit not found: ${editId}`);
32
25
  }
33
26
  return JSON.parse(readFileSync(bundlePath, 'utf8'));
34
27
  }
35
- export function recordEditPull(projectRoot, editId) {
36
- const index = readIndex(projectRoot);
28
+ export function recordEditPull(editId) {
29
+ const index = readIndex();
37
30
  const entry = index.edits.find((edit) => edit.id === editId);
38
31
  if (!entry)
39
32
  return;
40
33
  entry.pulls += 1;
41
34
  entry.lastPulledAt = new Date().toISOString();
42
- writeIndex(projectRoot, index);
35
+ writeIndex(index);
43
36
  }
44
- export function recordEditApplied(projectRoot, editId) {
45
- const index = readIndex(projectRoot);
37
+ export function recordEditApplied(editId) {
38
+ const index = readIndex();
46
39
  const entry = index.edits.find((edit) => edit.id === editId);
47
40
  if (!entry)
48
41
  return;
49
42
  entry.appliedAt = new Date().toISOString();
50
- writeIndex(projectRoot, index);
43
+ writeIndex(index);
51
44
  }
@@ -0,0 +1 @@
1
+ export declare function getOmpoEditsStorePath(): string;
@@ -0,0 +1,5 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ export function getOmpoEditsStorePath() {
4
+ return join(homedir(), '.ompo', 'edits');
5
+ }
package/dist/index.js CHANGED
@@ -3,26 +3,28 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { z } from 'zod';
5
5
  import { buildApplyPlan, explainEdit } from './apply-plan.js';
6
- import { findProjectRoot, listEdits, readEditBundle, recordEditApplied, recordEditPull } from './edit-store.js';
6
+ import { runCli } from './cli.js';
7
+ import { editsStoreReady, listEdits, readEditBundle, recordEditApplied, recordEditPull } from './edit-store.js';
8
+ import { getOmpoEditsStorePath } from './edits-path.js';
7
9
  const server = new McpServer({
8
10
  name: 'ompo-mcp-server',
9
- version: '0.1.0'
11
+ version: '0.1.3'
10
12
  });
11
- function requireProjectRoot() {
12
- const projectRoot = findProjectRoot();
13
- if (!projectRoot) {
14
- throw new Error('No Ompo edits found. Run Send in Ompo first to create .ompo/edits in your project.');
13
+ function requireEditsStore() {
14
+ const storePath = getOmpoEditsStorePath();
15
+ if (!editsStoreReady()) {
16
+ throw new Error('No Ompo edits found. Click Send in Ompo first to save an edit, then try again.');
15
17
  }
16
- return projectRoot;
18
+ return storePath;
17
19
  }
18
- server.tool('list_edits', 'List Ompo edit bundles saved in the current project', {}, async () => {
19
- const projectRoot = requireProjectRoot();
20
- const edits = listEdits(projectRoot);
20
+ server.tool('list_edits', 'List Ompo edit bundles saved on this machine', {}, async () => {
21
+ const storePath = requireEditsStore();
22
+ const edits = listEdits();
21
23
  return {
22
24
  content: [
23
25
  {
24
26
  type: 'text',
25
- text: JSON.stringify({ projectRoot, edits }, null, 2)
27
+ text: JSON.stringify({ storePath, edits }, null, 2)
26
28
  }
27
29
  ]
28
30
  };
@@ -30,9 +32,9 @@ server.tool('list_edits', 'List Ompo edit bundles saved in the current project',
30
32
  server.tool('get_edit', 'Load a specific Ompo edit bundle by id', {
31
33
  id: z.string().describe('Edit id, e.g. ed_8K42P')
32
34
  }, async ({ id }) => {
33
- const projectRoot = requireProjectRoot();
34
- const bundle = readEditBundle(projectRoot, id);
35
- recordEditPull(projectRoot, id);
35
+ requireEditsStore();
36
+ const bundle = readEditBundle(id);
37
+ recordEditPull(id);
36
38
  return {
37
39
  content: [
38
40
  {
@@ -45,9 +47,9 @@ server.tool('get_edit', 'Load a specific Ompo edit bundle by id', {
45
47
  server.tool('explain_edit', 'Summarize what an Ompo edit changes', {
46
48
  id: z.string().describe('Edit id, e.g. ed_8K42P')
47
49
  }, async ({ id }) => {
48
- const projectRoot = requireProjectRoot();
49
- const bundle = readEditBundle(projectRoot, id);
50
- recordEditPull(projectRoot, id);
50
+ requireEditsStore();
51
+ const bundle = readEditBundle(id);
52
+ recordEditPull(id);
51
53
  return {
52
54
  content: [
53
55
  {
@@ -57,19 +59,19 @@ server.tool('explain_edit', 'Summarize what an Ompo edit changes', {
57
59
  ]
58
60
  };
59
61
  });
60
- server.tool('apply_edit', 'Build an apply plan for an Ompo edit. The agent should execute the plan against source files and only change listed properties.', {
62
+ server.tool('apply_edit', 'Build an apply plan for an Ompo edit. The agent should execute the plan against source files in the open codebase and only change listed properties.', {
61
63
  id: z.string().describe('Edit id, e.g. ed_8K42P'),
62
64
  markApplied: z
63
65
  .boolean()
64
66
  .optional()
65
67
  .describe('Set true after the agent successfully applies the edit')
66
68
  }, async ({ id, markApplied }) => {
67
- const projectRoot = requireProjectRoot();
68
- const bundle = readEditBundle(projectRoot, id);
69
- recordEditPull(projectRoot, id);
69
+ requireEditsStore();
70
+ const bundle = readEditBundle(id);
71
+ recordEditPull(id);
70
72
  const plan = buildApplyPlan(bundle);
71
73
  if (markApplied) {
72
- recordEditApplied(projectRoot, id);
74
+ recordEditApplied(id);
73
75
  }
74
76
  return {
75
77
  content: [
@@ -81,6 +83,8 @@ server.tool('apply_edit', 'Build an apply plan for an Ompo edit. The agent shoul
81
83
  };
82
84
  });
83
85
  async function main() {
86
+ if (runCli(process.argv.slice(2)))
87
+ return;
84
88
  const transport = new StdioServerTransport();
85
89
  await server.connect(transport);
86
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ompo-design/mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server for applying Ompo visual edits to a codebase",
5
5
  "type": "module",
6
6
  "license": "MIT",