@ompo-design/mcp-server 0.1.6 → 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.
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.6'
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.6",
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": {