@ompo-design/mcp-server 0.1.6 → 0.1.9
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/cli.js +27 -2
- package/dist/edit-billing.d.ts +2 -0
- package/dist/edit-billing.js +75 -0
- package/dist/index.js +89 -42
- package/dist/session.d.ts +11 -0
- package/dist/session.js +36 -0
- package/dist/tokens.d.ts +6 -0
- package/dist/tokens.js +90 -0
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,10 @@ import { homedir } from 'os';
|
|
|
4
4
|
import { join, resolve } from 'path';
|
|
5
5
|
import { editsStoreReady } from './edit-store.js';
|
|
6
6
|
import { getOmpoEditsStorePath } from './edits-path.js';
|
|
7
|
+
import { readOmpoMcpSession } from './session.js';
|
|
8
|
+
import { readMcpTokenBalance } from './tokens.js';
|
|
7
9
|
const PACKAGE_NAME = '@ompo-design/mcp-server';
|
|
10
|
+
const PACKAGE_VERSION = '0.1.9';
|
|
8
11
|
const SERVER_NAME = 'ompo';
|
|
9
12
|
function resolveExecutable(name) {
|
|
10
13
|
try {
|
|
@@ -85,7 +88,7 @@ export function runCli(argv) {
|
|
|
85
88
|
return true;
|
|
86
89
|
}
|
|
87
90
|
if (command === 'doctor') {
|
|
88
|
-
runDoctor();
|
|
91
|
+
void runDoctor();
|
|
89
92
|
return true;
|
|
90
93
|
}
|
|
91
94
|
if (command === 'help' || command === '--help' || command === '-h') {
|
|
@@ -123,7 +126,7 @@ function runGlobalSetup() {
|
|
|
123
126
|
console.log(' 5. In Ompo, click Send on an edit before using the tools');
|
|
124
127
|
console.log(` Edits are stored in ${getOmpoEditsStorePath()}`);
|
|
125
128
|
}
|
|
126
|
-
function runDoctor() {
|
|
129
|
+
async function runDoctor() {
|
|
127
130
|
const projectRoot = resolve(process.cwd());
|
|
128
131
|
const configPath = join(projectRoot, '.mcp.json');
|
|
129
132
|
const desktopPath = claudeDesktopConfigPath();
|
|
@@ -131,6 +134,7 @@ function runDoctor() {
|
|
|
131
134
|
const storePath = getOmpoEditsStorePath();
|
|
132
135
|
console.log('Ompo MCP doctor');
|
|
133
136
|
console.log('');
|
|
137
|
+
console.log(`Package version: ${PACKAGE_VERSION}`);
|
|
134
138
|
console.log(`Current folder: ${projectRoot}`);
|
|
135
139
|
console.log(`Node: ${process.version}`);
|
|
136
140
|
console.log(`npx: ${resolveExecutable('npx')}`);
|
|
@@ -156,6 +160,26 @@ function runDoctor() {
|
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
console.log('');
|
|
163
|
+
const session = readOmpoMcpSession();
|
|
164
|
+
if (!session) {
|
|
165
|
+
console.log('Session: missing (open Ompo, sign in, and keep the app running)');
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(`Session: ready for user ${session.userId}`);
|
|
169
|
+
console.log(`Session updated: ${session.updatedAt}`);
|
|
170
|
+
if (!session.refreshToken) {
|
|
171
|
+
console.log('Session: missing refresh token (restart Ompo after updating)');
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const balance = await readMcpTokenBalance();
|
|
175
|
+
console.log(`Tokens: ${balance ?? 'unknown'} available`);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const message = error instanceof Error ? error.message : 'unknown error';
|
|
179
|
+
console.log(`Tokens: could not read balance (${message})`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
console.log('');
|
|
159
183
|
if (editsStoreReady()) {
|
|
160
184
|
console.log('Edits: ready (Send from Ompo to add more)');
|
|
161
185
|
}
|
|
@@ -163,6 +187,7 @@ function runDoctor() {
|
|
|
163
187
|
console.log('Edits: none yet (click Send in Ompo first)');
|
|
164
188
|
}
|
|
165
189
|
console.log('');
|
|
190
|
+
console.log('If tools skip token usage, quit and reopen Claude Code to reload MCP v0.1.9+');
|
|
166
191
|
console.log('Global install: npx @ompo-design/mcp-server setup-global');
|
|
167
192
|
}
|
|
168
193
|
function printProjectNextSteps(projectRoot) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { consumeMcpToken, McpTokenError, readMcpTokenBalance } from './tokens.js';
|
|
5
|
+
import { readOmpoMcpSession } from './session.js';
|
|
6
|
+
function requireSignedIn() {
|
|
7
|
+
if (!readOmpoMcpSession()) {
|
|
8
|
+
throw new McpTokenError('NOT_SIGNED_IN', 'Sign in to Ompo and keep the app open so your MCP session stays active.');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const BILLING_TTL_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
function getEditBillingPath() {
|
|
13
|
+
return join(homedir(), '.ompo', 'edit-billing.json');
|
|
14
|
+
}
|
|
15
|
+
function readBillingState() {
|
|
16
|
+
const billingPath = getEditBillingPath();
|
|
17
|
+
if (!existsSync(billingPath)) {
|
|
18
|
+
return { billedEdits: {} };
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(readFileSync(billingPath, 'utf8'));
|
|
22
|
+
return {
|
|
23
|
+
billedEdits: typeof parsed.billedEdits === 'object' && parsed.billedEdits !== null
|
|
24
|
+
? parsed.billedEdits
|
|
25
|
+
: {}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { billedEdits: {} };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function writeBillingState(state) {
|
|
33
|
+
const billingPath = getEditBillingPath();
|
|
34
|
+
mkdirSync(dirname(billingPath), { recursive: true });
|
|
35
|
+
writeFileSync(billingPath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
36
|
+
}
|
|
37
|
+
function pruneExpiredBilledEdits(billedEdits) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const pruned = {};
|
|
40
|
+
for (const [editId, billedAt] of Object.entries(billedEdits)) {
|
|
41
|
+
const billedTime = Date.parse(billedAt);
|
|
42
|
+
if (Number.isNaN(billedTime))
|
|
43
|
+
continue;
|
|
44
|
+
if (now - billedTime <= BILLING_TTL_MS) {
|
|
45
|
+
pruned[editId] = billedAt;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return pruned;
|
|
49
|
+
}
|
|
50
|
+
function isEditAlreadyBilled(editId) {
|
|
51
|
+
const state = readBillingState();
|
|
52
|
+
const billedEdits = pruneExpiredBilledEdits(state.billedEdits);
|
|
53
|
+
return editId in billedEdits;
|
|
54
|
+
}
|
|
55
|
+
function markEditBilled(editId) {
|
|
56
|
+
const state = readBillingState();
|
|
57
|
+
const billedEdits = pruneExpiredBilledEdits(state.billedEdits);
|
|
58
|
+
billedEdits[editId] = new Date().toISOString();
|
|
59
|
+
writeBillingState({ billedEdits });
|
|
60
|
+
}
|
|
61
|
+
export async function consumeMcpTokenForEdit(toolName, editId) {
|
|
62
|
+
requireSignedIn();
|
|
63
|
+
if (isEditAlreadyBilled(editId)) {
|
|
64
|
+
const balance = await readMcpTokenBalance();
|
|
65
|
+
return balance ?? 0;
|
|
66
|
+
}
|
|
67
|
+
const tokensRemaining = await consumeMcpToken(toolName);
|
|
68
|
+
markEditBilled(editId);
|
|
69
|
+
return tokensRemaining;
|
|
70
|
+
}
|
|
71
|
+
export async function requireMcpSessionBalance() {
|
|
72
|
+
requireSignedIn();
|
|
73
|
+
const balance = await readMcpTokenBalance();
|
|
74
|
+
return balance ?? 0;
|
|
75
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,13 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { buildApplyPlan, explainEdit } from './apply-plan.js';
|
|
6
6
|
import { runCli } from './cli.js';
|
|
7
|
+
import { consumeMcpTokenForEdit, requireMcpSessionBalance } from './edit-billing.js';
|
|
7
8
|
import { editsStoreReady, listEdits, readEditBundle, recordEditApplied, recordEditPull } from './edit-store.js';
|
|
8
9
|
import { getOmpoEditsStorePath } from './edits-path.js';
|
|
10
|
+
import { McpTokenError } from './tokens.js';
|
|
9
11
|
const server = new McpServer({
|
|
10
12
|
name: 'ompo-mcp-server',
|
|
11
|
-
version: '0.1.
|
|
13
|
+
version: '0.1.9'
|
|
12
14
|
});
|
|
13
15
|
function requireEditsStore() {
|
|
14
16
|
const storePath = getOmpoEditsStorePath();
|
|
@@ -17,47 +19,85 @@ function requireEditsStore() {
|
|
|
17
19
|
}
|
|
18
20
|
return storePath;
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
22
|
+
async function withMcpTokenUseForEdit(toolName, editId, handler) {
|
|
23
|
+
const tokensRemaining = await consumeMcpTokenForEdit(toolName, editId);
|
|
24
|
+
return handler(tokensRemaining);
|
|
25
|
+
}
|
|
26
|
+
function tokenErrorResult(error) {
|
|
27
|
+
const message = error instanceof McpTokenError
|
|
28
|
+
? error.message
|
|
29
|
+
: 'Could not verify MCP token balance. Check your connection and try again.';
|
|
23
30
|
return {
|
|
24
31
|
content: [
|
|
25
32
|
{
|
|
26
33
|
type: 'text',
|
|
27
|
-
text:
|
|
34
|
+
text: message
|
|
28
35
|
}
|
|
29
|
-
]
|
|
36
|
+
],
|
|
37
|
+
isError: true
|
|
30
38
|
};
|
|
39
|
+
}
|
|
40
|
+
server.tool('list_edits', 'List Ompo edit bundles saved on this machine', {}, async () => {
|
|
41
|
+
try {
|
|
42
|
+
const storePath = requireEditsStore();
|
|
43
|
+
const edits = listEdits();
|
|
44
|
+
const tokensRemaining = await requireMcpSessionBalance();
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: 'text',
|
|
49
|
+
text: JSON.stringify({ storePath, edits, tokensRemaining }, null, 2)
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
try {
|
|
62
|
+
return await withMcpTokenUseForEdit('get_edit', id, 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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
try {
|
|
84
|
+
return await withMcpTokenUseForEdit('explain_edit', id, 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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
content: [
|
|
78
|
-
{
|
|
79
|
-
type: 'text',
|
|
80
|
-
text: JSON.stringify(plan, null, 2)
|
|
109
|
+
try {
|
|
110
|
+
return await withMcpTokenUseForEdit('apply_edit', id, 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,11 @@
|
|
|
1
|
+
export type OmpoMcpSession = {
|
|
2
|
+
userId: string;
|
|
3
|
+
accessToken: string;
|
|
4
|
+
refreshToken?: string;
|
|
5
|
+
supabaseUrl: string;
|
|
6
|
+
supabaseAnonKey: string;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function getOmpoMcpSessionPath(): string;
|
|
10
|
+
export declare function readOmpoMcpSession(): OmpoMcpSession | null;
|
|
11
|
+
export declare function writeOmpoMcpSession(session: OmpoMcpSession): void;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { dirname, 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
|
+
refreshToken: typeof parsed.refreshToken === 'string' ? parsed.refreshToken : undefined,
|
|
23
|
+
supabaseUrl: parsed.supabaseUrl,
|
|
24
|
+
supabaseAnonKey: parsed.supabaseAnonKey,
|
|
25
|
+
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : new Date(0).toISOString()
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function writeOmpoMcpSession(session) {
|
|
33
|
+
const sessionPath = getOmpoMcpSessionPath();
|
|
34
|
+
mkdirSync(dirname(sessionPath), { recursive: true });
|
|
35
|
+
writeFileSync(sessionPath, `${JSON.stringify({ ...session, updatedAt: new Date().toISOString() }, null, 2)}\n`, 'utf8');
|
|
36
|
+
}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
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 readMcpTokenBalance(): Promise<number | null>;
|
|
6
|
+
export declare function consumeMcpToken(toolName: string): Promise<number>;
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createClient } from '@supabase/supabase-js';
|
|
2
|
+
import { readOmpoMcpSession, writeOmpoMcpSession } 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
|
+
async function createAuthedSupabase(session) {
|
|
22
|
+
const supabase = createClient(session.supabaseUrl, session.supabaseAnonKey, {
|
|
23
|
+
auth: {
|
|
24
|
+
persistSession: false,
|
|
25
|
+
autoRefreshToken: false,
|
|
26
|
+
detectSessionInUrl: false
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (session.refreshToken) {
|
|
30
|
+
const { data, error } = await supabase.auth.setSession({
|
|
31
|
+
access_token: session.accessToken,
|
|
32
|
+
refresh_token: session.refreshToken
|
|
33
|
+
});
|
|
34
|
+
if (!error && data.session) {
|
|
35
|
+
if (data.session.access_token !== session.accessToken ||
|
|
36
|
+
data.session.refresh_token !== session.refreshToken) {
|
|
37
|
+
writeOmpoMcpSession({
|
|
38
|
+
...session,
|
|
39
|
+
accessToken: data.session.access_token,
|
|
40
|
+
refreshToken: data.session.refresh_token
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return supabase;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return createClient(session.supabaseUrl, session.supabaseAnonKey, {
|
|
47
|
+
auth: {
|
|
48
|
+
persistSession: false,
|
|
49
|
+
autoRefreshToken: false,
|
|
50
|
+
detectSessionInUrl: false
|
|
51
|
+
},
|
|
52
|
+
global: {
|
|
53
|
+
headers: {
|
|
54
|
+
Authorization: `Bearer ${session.accessToken}`
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function readMcpTokenBalance() {
|
|
60
|
+
const session = readOmpoMcpSession();
|
|
61
|
+
if (!session)
|
|
62
|
+
return null;
|
|
63
|
+
const supabase = await createAuthedSupabase(session);
|
|
64
|
+
const { data, error } = await supabase
|
|
65
|
+
.from('user_token_balances')
|
|
66
|
+
.select('tokens_available')
|
|
67
|
+
.eq('user_id', session.userId)
|
|
68
|
+
.maybeSingle();
|
|
69
|
+
if (error)
|
|
70
|
+
throw mapRpcError(error.message);
|
|
71
|
+
return data?.tokens_available ?? null;
|
|
72
|
+
}
|
|
73
|
+
export async function consumeMcpToken(toolName) {
|
|
74
|
+
const session = readOmpoMcpSession();
|
|
75
|
+
if (!session) {
|
|
76
|
+
throw new McpTokenError('NOT_SIGNED_IN', 'Sign in to Ompo and keep the app open so your MCP session stays active.');
|
|
77
|
+
}
|
|
78
|
+
const supabase = await createAuthedSupabase(session);
|
|
79
|
+
const { data, error } = await supabase.rpc('consume_mcp_token', {
|
|
80
|
+
p_tool_name: toolName
|
|
81
|
+
});
|
|
82
|
+
if (error) {
|
|
83
|
+
throw mapRpcError(error.message);
|
|
84
|
+
}
|
|
85
|
+
const tokensRemaining = data?.tokens_remaining;
|
|
86
|
+
if (typeof tokensRemaining !== 'number') {
|
|
87
|
+
throw new McpTokenError('TOKEN_SERVICE_UNAVAILABLE', 'Could not verify MCP token balance. Check your connection and try again.');
|
|
88
|
+
}
|
|
89
|
+
return tokensRemaining;
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ompo-design/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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": "
|
|
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": {
|