@ompo-design/mcp-server 0.1.5 → 0.1.7

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.
@@ -1,7 +1,8 @@
1
1
  const OMPO_GLOSSARY = {
2
2
  gap: `Ompo "Gap" is the space between children inside a flex or grid container. Apply \`gap\` on the parent layout element (not on children). The parent needs \`display: flex\` or \`display: grid\`. Example: gap "16px" → CSS \`gap: 16px\` or Tailwind \`gap-4\`.`,
3
3
  fill: `Ompo "Fill" means an element should expand to consume free space inside its parent. Fill is NOT a CSS property — read \`widthMode: fill\` / \`heightMode: fill\` together with \`ensureParentFlex\`, \`flexGrow\`, \`flexShrink\`, \`flexBasis\`, and \`alignSelf\` on the same selector. Parent must be \`display: flex\`. Main axis (direction of flex): use \`flex: 1 1 0\` (grow/shrink/basis). Cross axis: use \`align-self: stretch\` and remove fixed width/height on that axis. If \`ensureParentFlex\` is present, make the parent a flex container with that direction first.`,
4
- fit: `Ompo "Fit" means size to content. Map \`widthMode: fit\` → \`width: fit-content\`, \`heightMode: fit\` → \`height: fit-content\` (or the project's equivalent).`
4
+ fit: `Ompo "Fit" means size to content. Map \`widthMode: fit\` → \`width: fit-content\`, \`heightMode: fit\` → \`height: fit-content\` (or the project's equivalent).`,
5
+ imageFill: `Ompo image background fill: copy the source file from \`backgroundImageSource\` into the project (e.g. public/images/), then set \`background-image\` to a project-relative url(), with \`background-size: cover\`, \`background-position: center\`, and \`background-repeat: no-repeat\`. Do not commit file:// URLs to source.`
5
6
  };
6
7
  function formatChangedValue(property, value) {
7
8
  const text = String(value);
@@ -103,6 +104,35 @@ function styleSuggestionForProperty(property, value, changed) {
103
104
  strategy: 'css-rule',
104
105
  notes: 'Supporting constraint for Ompo Fill sizing. max-width/max-height: none allows the element to grow.'
105
106
  };
107
+ case 'backgroundImageSource':
108
+ return {
109
+ property,
110
+ value: formatChangedValue(property, value),
111
+ strategy: 'component-markup',
112
+ notes: `${OMPO_GLOSSARY.imageFill} Read this file from the user's machine (${String(value)}), copy it into the repo, and reference the copied asset in CSS.`
113
+ };
114
+ case 'backgroundImage':
115
+ if (changed.backgroundImageSource) {
116
+ return {
117
+ property,
118
+ value: formatChangedValue(property, value),
119
+ strategy: 'css-rule',
120
+ notes: `${OMPO_GLOSSARY.imageFill} Apply after copying the file from backgroundImageSource.`
121
+ };
122
+ }
123
+ break;
124
+ case 'backgroundSize':
125
+ case 'backgroundPosition':
126
+ case 'backgroundRepeat':
127
+ if (changed.backgroundImageSource) {
128
+ return {
129
+ property,
130
+ value: formatChangedValue(property, value),
131
+ strategy: 'css-rule',
132
+ notes: `Part of Ompo image background fill (${OMPO_GLOSSARY.imageFill}).`
133
+ };
134
+ }
135
+ break;
106
136
  }
107
137
  return {
108
138
  property,
package/dist/index.js CHANGED
@@ -6,9 +6,10 @@ import { buildApplyPlan, explainEdit } from './apply-plan.js';
6
6
  import { runCli } from './cli.js';
7
7
  import { editsStoreReady, listEdits, readEditBundle, recordEditApplied, recordEditPull } from './edit-store.js';
8
8
  import { getOmpoEditsStorePath } from './edits-path.js';
9
+ import { consumeMcpToken, McpTokenError } from './tokens.js';
9
10
  const server = new McpServer({
10
11
  name: 'ompo-mcp-server',
11
- version: '0.1.5'
12
+ version: '0.1.7'
12
13
  });
13
14
  function requireEditsStore() {
14
15
  const storePath = getOmpoEditsStorePath();
@@ -17,47 +18,86 @@ function requireEditsStore() {
17
18
  }
18
19
  return storePath;
19
20
  }
20
- server.tool('list_edits', 'List Ompo edit bundles saved on this machine', {}, async () => {
21
- const storePath = requireEditsStore();
22
- const edits = listEdits();
21
+ async function withMcpTokenUse(toolName, handler) {
22
+ const tokensRemaining = await consumeMcpToken(toolName);
23
+ return handler(tokensRemaining);
24
+ }
25
+ function tokenErrorResult(error) {
26
+ const message = error instanceof McpTokenError
27
+ ? error.message
28
+ : 'Could not verify MCP token balance. Check your connection and try again.';
23
29
  return {
24
30
  content: [
25
31
  {
26
32
  type: 'text',
27
- text: JSON.stringify({ storePath, edits }, null, 2)
33
+ text: message
28
34
  }
29
- ]
35
+ ],
36
+ isError: true
30
37
  };
38
+ }
39
+ server.tool('list_edits', 'List Ompo edit bundles saved on this machine', {}, async () => {
40
+ try {
41
+ return await withMcpTokenUse('list_edits', async (tokensRemaining) => {
42
+ const storePath = requireEditsStore();
43
+ const edits = listEdits();
44
+ return {
45
+ content: [
46
+ {
47
+ type: 'text',
48
+ text: JSON.stringify({ storePath, edits, tokensRemaining }, null, 2)
49
+ }
50
+ ]
51
+ };
52
+ });
53
+ }
54
+ catch (error) {
55
+ return tokenErrorResult(error);
56
+ }
31
57
  });
32
58
  server.tool('get_edit', 'Load a specific Ompo edit bundle by id', {
33
59
  id: z.string().describe('Edit id, e.g. ed_8K42P')
34
60
  }, async ({ id }) => {
35
- requireEditsStore();
36
- const bundle = readEditBundle(id);
37
- recordEditPull(id);
38
- return {
39
- content: [
40
- {
41
- type: 'text',
42
- text: JSON.stringify(bundle, null, 2)
43
- }
44
- ]
45
- };
61
+ try {
62
+ return await withMcpTokenUse('get_edit', async (tokensRemaining) => {
63
+ requireEditsStore();
64
+ const bundle = readEditBundle(id);
65
+ recordEditPull(id);
66
+ return {
67
+ content: [
68
+ {
69
+ type: 'text',
70
+ text: `${JSON.stringify(bundle, null, 2)}\n\nTokens remaining: ${tokensRemaining}`
71
+ }
72
+ ]
73
+ };
74
+ });
75
+ }
76
+ catch (error) {
77
+ return tokenErrorResult(error);
78
+ }
46
79
  });
47
80
  server.tool('explain_edit', 'Summarize what an Ompo edit changes', {
48
81
  id: z.string().describe('Edit id, e.g. ed_8K42P')
49
82
  }, async ({ id }) => {
50
- requireEditsStore();
51
- const bundle = readEditBundle(id);
52
- recordEditPull(id);
53
- return {
54
- content: [
55
- {
56
- type: 'text',
57
- text: explainEdit(bundle)
58
- }
59
- ]
60
- };
83
+ try {
84
+ return await withMcpTokenUse('explain_edit', async (tokensRemaining) => {
85
+ requireEditsStore();
86
+ const bundle = readEditBundle(id);
87
+ recordEditPull(id);
88
+ return {
89
+ content: [
90
+ {
91
+ type: 'text',
92
+ text: `${explainEdit(bundle)}\n\nTokens remaining: ${tokensRemaining}`
93
+ }
94
+ ]
95
+ };
96
+ });
97
+ }
98
+ catch (error) {
99
+ return tokenErrorResult(error);
100
+ }
61
101
  });
62
102
  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.', {
63
103
  id: z.string().describe('Edit id, e.g. ed_8K42P'),
@@ -66,21 +106,28 @@ server.tool('apply_edit', 'Build an apply plan for an Ompo edit. The agent shoul
66
106
  .optional()
67
107
  .describe('Set true after the agent successfully applies the edit')
68
108
  }, async ({ id, markApplied }) => {
69
- requireEditsStore();
70
- const bundle = readEditBundle(id);
71
- recordEditPull(id);
72
- const plan = buildApplyPlan(bundle);
73
- if (markApplied) {
74
- recordEditApplied(id);
75
- }
76
- return {
77
- content: [
78
- {
79
- type: 'text',
80
- text: JSON.stringify(plan, null, 2)
109
+ try {
110
+ return await withMcpTokenUse('apply_edit', async (tokensRemaining) => {
111
+ requireEditsStore();
112
+ const bundle = readEditBundle(id);
113
+ recordEditPull(id);
114
+ const plan = buildApplyPlan(bundle);
115
+ if (markApplied) {
116
+ recordEditApplied(id);
81
117
  }
82
- ]
83
- };
118
+ return {
119
+ content: [
120
+ {
121
+ type: 'text',
122
+ text: JSON.stringify({ ...plan, tokensRemaining }, null, 2)
123
+ }
124
+ ]
125
+ };
126
+ });
127
+ }
128
+ catch (error) {
129
+ return tokenErrorResult(error);
130
+ }
84
131
  });
85
132
  async function main() {
86
133
  if (runCli(process.argv.slice(2)))
@@ -0,0 +1,9 @@
1
+ export type OmpoMcpSession = {
2
+ userId: string;
3
+ accessToken: string;
4
+ supabaseUrl: string;
5
+ supabaseAnonKey: string;
6
+ updatedAt: string;
7
+ };
8
+ export declare function getOmpoMcpSessionPath(): string;
9
+ export declare function readOmpoMcpSession(): OmpoMcpSession | null;
@@ -0,0 +1,30 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ export function getOmpoMcpSessionPath() {
5
+ return join(homedir(), '.ompo', 'session.json');
6
+ }
7
+ export function readOmpoMcpSession() {
8
+ const sessionPath = getOmpoMcpSessionPath();
9
+ if (!existsSync(sessionPath))
10
+ return null;
11
+ try {
12
+ const parsed = JSON.parse(readFileSync(sessionPath, 'utf8'));
13
+ if (typeof parsed.userId !== 'string' ||
14
+ typeof parsed.accessToken !== 'string' ||
15
+ typeof parsed.supabaseUrl !== 'string' ||
16
+ typeof parsed.supabaseAnonKey !== 'string') {
17
+ return null;
18
+ }
19
+ return {
20
+ userId: parsed.userId,
21
+ accessToken: parsed.accessToken,
22
+ supabaseUrl: parsed.supabaseUrl,
23
+ supabaseAnonKey: parsed.supabaseAnonKey,
24
+ updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : new Date(0).toISOString()
25
+ };
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
@@ -0,0 +1,5 @@
1
+ export declare class McpTokenError extends Error {
2
+ code: 'NOT_SIGNED_IN' | 'INSUFFICIENT_TOKENS' | 'TOKEN_SERVICE_UNAVAILABLE';
3
+ constructor(code: McpTokenError['code'], message: string);
4
+ }
5
+ export declare function consumeMcpToken(toolName: string): Promise<number>;
package/dist/tokens.js ADDED
@@ -0,0 +1,49 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+ import { readOmpoMcpSession } from './session.js';
3
+ export class McpTokenError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = 'McpTokenError';
8
+ this.code = code;
9
+ }
10
+ }
11
+ function mapRpcError(message) {
12
+ const normalized = message.toUpperCase();
13
+ if (normalized.includes('NOT_AUTHENTICATED') || normalized.includes('JWT')) {
14
+ return new McpTokenError('NOT_SIGNED_IN', 'Sign in to Ompo and keep the app open so your MCP session stays active.');
15
+ }
16
+ if (normalized.includes('INSUFFICIENT_TOKENS')) {
17
+ return new McpTokenError('INSUFFICIENT_TOKENS', 'You are out of MCP tokens. Add more tokens to your account before using Ompo MCP tools.');
18
+ }
19
+ return new McpTokenError('TOKEN_SERVICE_UNAVAILABLE', 'Could not verify MCP token balance. Check your connection and try again.');
20
+ }
21
+ export async function consumeMcpToken(toolName) {
22
+ const session = readOmpoMcpSession();
23
+ if (!session) {
24
+ throw new McpTokenError('NOT_SIGNED_IN', 'Sign in to Ompo and keep the app open so your MCP session stays active.');
25
+ }
26
+ const supabase = createClient(session.supabaseUrl, session.supabaseAnonKey, {
27
+ auth: {
28
+ persistSession: false,
29
+ autoRefreshToken: false,
30
+ detectSessionInUrl: false
31
+ },
32
+ global: {
33
+ headers: {
34
+ Authorization: `Bearer ${session.accessToken}`
35
+ }
36
+ }
37
+ });
38
+ const { data, error } = await supabase.rpc('consume_mcp_token', {
39
+ p_tool_name: toolName
40
+ });
41
+ if (error) {
42
+ throw mapRpcError(error.message);
43
+ }
44
+ const tokensRemaining = data?.tokens_remaining;
45
+ if (typeof tokensRemaining !== 'number') {
46
+ throw new McpTokenError('TOKEN_SERVICE_UNAVAILABLE', 'Could not verify MCP token balance. Check your connection and try again.');
47
+ }
48
+ return tokensRemaining;
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ompo-design/mcp-server",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "MCP server for applying Ompo visual edits to a codebase",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,7 +12,7 @@
12
12
  "claude-code"
13
13
  ],
14
14
  "bin": {
15
- "ompo-mcp": "./dist/index.js"
15
+ "ompo-mcp": "dist/index.js"
16
16
  },
17
17
  "main": "./dist/index.js",
18
18
  "files": [
@@ -25,6 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.12.0",
28
+ "@supabase/supabase-js": "^2.49.8",
28
29
  "zod": "^3.24.2"
29
30
  },
30
31
  "devDependencies": {