brandguideai-mcp 1.0.1 → 1.0.2

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
@@ -62,14 +62,28 @@ Responses include expert answers with source citations from brand strategy books
62
62
 
63
63
  Every user gets 4 free queries. No account or API key required — a temporary session is created automatically on first use.
64
64
 
65
- After 8 queries, the tool returns upgrade links:
65
+ After 4 queries, the tool returns upgrade links:
66
66
  - **Creator** — 300 queries/month
67
67
  - **Pro** — 1,000 queries/month
68
68
  - **Mentor+** — unlimited
69
69
 
70
70
  ## For existing brandguide/AI subscribers
71
71
 
72
- Your subscription quota works automatically. The MCP server uses the same account system as the web app at [ai.brandguide.hu](https://ai.brandguide.hu).
72
+ Log in once to use your subscription quota:
73
+
74
+ ```bash
75
+ npx brandguideai-mcp login
76
+ ```
77
+
78
+ Enter your brandguide/AI email and password. A refresh token is stored locally at `~/.brandguide/credentials.json` — your password is never saved.
79
+
80
+ After login, all MCP queries use your subscription quota instead of the 4 free queries.
81
+
82
+ To log out:
83
+
84
+ ```bash
85
+ npx brandguideai-mcp logout
86
+ ```
73
87
 
74
88
  ## How it works
75
89
 
package/dist/auth.d.ts CHANGED
@@ -1,2 +1,4 @@
1
+ export declare function clearCredentials(): void;
2
+ export declare function loginFlow(): Promise<void>;
1
3
  export declare function getAccessToken(): Promise<string>;
2
4
  export declare function getStoredEmail(): string;
package/dist/auth.js CHANGED
@@ -1,22 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearCredentials = clearCredentials;
4
+ exports.loginFlow = loginFlow;
3
5
  exports.getAccessToken = getAccessToken;
4
6
  exports.getStoredEmail = getStoredEmail;
5
7
  const supabase_js_1 = require("@supabase/supabase-js");
6
8
  const node_fs_1 = require("node:fs");
7
9
  const node_path_1 = require("node:path");
8
10
  const node_os_1 = require("node:os");
11
+ const node_readline_1 = require("node:readline");
9
12
  const constants_js_1 = require("./constants.js");
10
13
  const CREDENTIALS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.brandguide');
11
14
  const CREDENTIALS_FILE = (0, node_path_1.join)(CREDENTIALS_DIR, 'credentials.json');
12
15
  let cachedSession = null;
16
+ function createSupabaseClient() {
17
+ return (0, supabase_js_1.createClient)(constants_js_1.SUPABASE_URL, constants_js_1.SUPABASE_ANON_KEY, {
18
+ auth: { persistSession: false, autoRefreshToken: false },
19
+ });
20
+ }
13
21
  function loadCredentials() {
14
22
  try {
15
23
  if (!(0, node_fs_1.existsSync)(CREDENTIALS_FILE))
16
24
  return null;
17
25
  const raw = (0, node_fs_1.readFileSync)(CREDENTIALS_FILE, 'utf-8');
18
26
  const data = JSON.parse(raw);
19
- if (data.email && data.password && data.userId)
27
+ if (data.email && (data.password || data.refreshToken) && data.userId)
20
28
  return data;
21
29
  return null;
22
30
  }
@@ -35,6 +43,55 @@ function saveCredentials(creds) {
35
43
  console.error('[auth] Failed to save credentials:', err);
36
44
  }
37
45
  }
46
+ function clearCredentials() {
47
+ try {
48
+ if ((0, node_fs_1.existsSync)(CREDENTIALS_FILE)) {
49
+ (0, node_fs_1.unlinkSync)(CREDENTIALS_FILE);
50
+ }
51
+ }
52
+ catch { }
53
+ }
54
+ async function prompt(question, hidden = false) {
55
+ const rl = (0, node_readline_1.createInterface)({ input: process.stdin, output: process.stderr });
56
+ return new Promise((resolve) => {
57
+ if (hidden) {
58
+ process.stderr.write(question);
59
+ let input = '';
60
+ const stdin = process.stdin;
61
+ const wasRaw = stdin.isRaw;
62
+ if (stdin.isTTY)
63
+ stdin.setRawMode(true);
64
+ stdin.resume();
65
+ const onData = (char) => {
66
+ const c = char.toString();
67
+ if (c === '\n' || c === '\r') {
68
+ if (stdin.isTTY)
69
+ stdin.setRawMode(wasRaw ?? false);
70
+ stdin.removeListener('data', onData);
71
+ process.stderr.write('\n');
72
+ rl.close();
73
+ resolve(input);
74
+ }
75
+ else if (c === '\u0003') {
76
+ process.exit(1);
77
+ }
78
+ else if (c === '\u007f' || c === '\b') {
79
+ input = input.slice(0, -1);
80
+ }
81
+ else {
82
+ input += c;
83
+ }
84
+ };
85
+ stdin.on('data', onData);
86
+ }
87
+ else {
88
+ rl.question(question, (answer) => {
89
+ rl.close();
90
+ resolve(answer.trim());
91
+ });
92
+ }
93
+ });
94
+ }
38
95
  async function createTempAccount() {
39
96
  const response = await fetch(constants_js_1.CREATE_TEMP_SESSION_URL, {
40
97
  method: 'POST',
@@ -60,16 +117,61 @@ async function createTempAccount() {
60
117
  }
61
118
  return { email: data.email, password: data.password, userId: data.userId };
62
119
  }
63
- async function signIn(email, password) {
64
- const supabase = (0, supabase_js_1.createClient)(constants_js_1.SUPABASE_URL, constants_js_1.SUPABASE_ANON_KEY, {
65
- auth: { persistSession: false, autoRefreshToken: false },
66
- });
120
+ async function signInWithPassword(email, password) {
121
+ const supabase = createSupabaseClient();
67
122
  const { data, error } = await supabase.auth.signInWithPassword({ email, password });
68
123
  if (error || !data.session) {
69
124
  throw new Error(error?.message || 'Sign-in failed');
70
125
  }
71
126
  return data.session;
72
127
  }
128
+ async function signInWithRefreshToken(refreshToken) {
129
+ const supabase = createSupabaseClient();
130
+ const { data, error } = await supabase.auth.refreshSession({ refresh_token: refreshToken });
131
+ if (error || !data.session) {
132
+ throw new Error(error?.message || 'Token refresh failed');
133
+ }
134
+ return data.session;
135
+ }
136
+ async function loginFlow() {
137
+ console.error('\nbrandguide/AI MCP — Login\n');
138
+ const email = await prompt('Email: ');
139
+ if (!email) {
140
+ console.error('Email is required.');
141
+ process.exit(1);
142
+ }
143
+ const password = await prompt('Password: ', true);
144
+ if (!password) {
145
+ console.error('Password is required.');
146
+ process.exit(1);
147
+ }
148
+ try {
149
+ const session = await signInWithPassword(email, password);
150
+ const userId = session.user.id;
151
+ // Fetch quota info
152
+ const supabase = createSupabaseClient();
153
+ const { data: quota } = await supabase.rpc('get_remaining_questions', { p_user_id: userId });
154
+ const row = Array.isArray(quota) ? quota[0] : quota;
155
+ const tier = row?.tier || 'freemium';
156
+ const remaining = row?.remaining ?? 0;
157
+ const total = row?.total ?? 0;
158
+ saveCredentials({
159
+ email,
160
+ refreshToken: session.refresh_token,
161
+ userId,
162
+ tier,
163
+ });
164
+ console.error(`\nLogged in as ${email}`);
165
+ console.error(`Tier: ${tier} | Quota: ${remaining}/${total} remaining`);
166
+ console.error(`\nCredentials saved to ~/.brandguide/credentials.json`);
167
+ console.error(`Your subscription quota will be used for MCP queries.\n`);
168
+ }
169
+ catch (err) {
170
+ const message = err instanceof Error ? err.message : 'Login failed';
171
+ console.error(`\nLogin failed: ${message}\n`);
172
+ process.exit(1);
173
+ }
174
+ }
73
175
  async function getAccessToken() {
74
176
  // Check if cached session is still valid (5 min buffer)
75
177
  if (cachedSession) {
@@ -81,22 +183,30 @@ async function getAccessToken() {
81
183
  }
82
184
  // Try stored credentials
83
185
  let creds = loadCredentials();
84
- if (!creds) {
85
- // Create new temp account
86
- creds = await createTempAccount();
87
- saveCredentials(creds);
88
- }
89
- try {
90
- cachedSession = await signIn(creds.email, creds.password);
91
- return cachedSession.access_token;
92
- }
93
- catch {
94
- // Credentials might be stale (account deleted), create new account
95
- creds = await createTempAccount();
96
- saveCredentials(creds);
97
- cachedSession = await signIn(creds.email, creds.password);
98
- return cachedSession.access_token;
186
+ if (creds) {
187
+ try {
188
+ // Priority 1: Refresh token (logged-in user)
189
+ if (creds.refreshToken) {
190
+ cachedSession = await signInWithRefreshToken(creds.refreshToken);
191
+ // Update stored refresh token (it rotates)
192
+ saveCredentials({ ...creds, refreshToken: cachedSession.refresh_token });
193
+ return cachedSession.access_token;
194
+ }
195
+ // Priority 2: Email + password (temp account)
196
+ if (creds.password) {
197
+ cachedSession = await signInWithPassword(creds.email, creds.password);
198
+ return cachedSession.access_token;
199
+ }
200
+ }
201
+ catch {
202
+ // Credentials stale — fall through to create new temp account
203
+ }
99
204
  }
205
+ // Priority 3: Auto-create temp account
206
+ creds = await createTempAccount();
207
+ saveCredentials(creds);
208
+ cachedSession = await signInWithPassword(creds.email, creds.password);
209
+ return cachedSession.access_token;
100
210
  }
101
211
  function getStoredEmail() {
102
212
  const creds = loadCredentials();
package/dist/index.js CHANGED
@@ -1,70 +1,81 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
- const client_js_1 = require("./client.js");
8
- const server = new index_js_1.Server({ name: 'brandguideai-mcp', version: '1.0.0' }, { capabilities: { tools: {} } });
9
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
10
- tools: [
11
- {
12
- name: 'search_brand_knowledge',
13
- description: "Search brandguide/AI's brand strategy knowledge base — 1,000+ pages of methodology, design guidelines, and expert video transcripts. Works in Hungarian and English.",
14
- inputSchema: {
15
- type: 'object',
16
- properties: {
17
- query: {
18
- type: 'string',
19
- description: 'Your brand strategy question',
4
+ const auth_js_1 = require("./auth.js");
5
+ // Handle login/logout subcommands before starting MCP server
6
+ const command = process.argv[2];
7
+ if (command === 'login') {
8
+ (0, auth_js_1.loginFlow)().then(() => process.exit(0)).catch(() => process.exit(1));
9
+ }
10
+ else if (command === 'logout') {
11
+ (0, auth_js_1.clearCredentials)();
12
+ console.error('Logged out. Credentials removed from ~/.brandguide/credentials.json');
13
+ process.exit(0);
14
+ }
15
+ else {
16
+ // Start MCP server
17
+ startServer();
18
+ }
19
+ async function startServer() {
20
+ const { Server } = await import('@modelcontextprotocol/sdk/server/index.js');
21
+ const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
22
+ const { CallToolRequestSchema, ListToolsRequestSchema, } = await import('@modelcontextprotocol/sdk/types.js');
23
+ const { searchBrandKnowledge } = await import('./client.js');
24
+ const server = new Server({ name: 'brandguideai-mcp', version: '1.0.2' }, { capabilities: { tools: {} } });
25
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
26
+ tools: [
27
+ {
28
+ name: 'search_brand_knowledge',
29
+ description: "Search brandguide/AI's brand strategy knowledge base — 1,000+ pages of methodology, design guidelines, and expert video transcripts. Works in Hungarian and English.",
30
+ inputSchema: {
31
+ type: 'object',
32
+ properties: {
33
+ query: {
34
+ type: 'string',
35
+ description: 'Your brand strategy question',
36
+ },
20
37
  },
38
+ required: ['query'],
21
39
  },
22
- required: ['query'],
23
40
  },
24
- },
25
- ],
26
- }));
27
- server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
28
- if (request.params.name !== 'search_brand_knowledge') {
29
- return {
30
- content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
31
- isError: true,
32
- };
33
- }
34
- const query = String(request.params.arguments?.query || '').trim();
35
- if (!query) {
36
- return {
37
- content: [{ type: 'text', text: 'Query is required.' }],
38
- isError: true,
39
- };
40
- }
41
- try {
42
- const result = await (0, client_js_1.searchBrandKnowledge)(query);
43
- if (result.quotaExceeded) {
41
+ ],
42
+ }));
43
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
44
+ if (request.params.name !== 'search_brand_knowledge') {
45
+ return {
46
+ content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
47
+ isError: true,
48
+ };
49
+ }
50
+ const query = String(request.params.arguments?.query || '').trim();
51
+ if (!query) {
52
+ return {
53
+ content: [{ type: 'text', text: 'Query is required.' }],
54
+ isError: true,
55
+ };
56
+ }
57
+ try {
58
+ const result = await searchBrandKnowledge(query);
59
+ if (result.quotaExceeded) {
60
+ return {
61
+ content: [{ type: 'text', text: result.answer }],
62
+ };
63
+ }
64
+ const footer = result.remaining !== null && result.total !== null
65
+ ? `\n\n[${result.remaining}/${result.total} queries remaining]`
66
+ : '';
67
+ return {
68
+ content: [{ type: 'text', text: result.answer + footer }],
69
+ };
70
+ }
71
+ catch (err) {
72
+ const message = err instanceof Error ? err.message : 'Unknown error';
44
73
  return {
45
- content: [{ type: 'text', text: result.answer }],
74
+ content: [{ type: 'text', text: `Error: ${message}` }],
75
+ isError: true,
46
76
  };
47
77
  }
48
- const footer = result.remaining !== null && result.total !== null
49
- ? `\n\n[${result.remaining}/${result.total} queries remaining]`
50
- : '';
51
- return {
52
- content: [{ type: 'text', text: result.answer + footer }],
53
- };
54
- }
55
- catch (err) {
56
- const message = err instanceof Error ? err.message : 'Unknown error';
57
- return {
58
- content: [{ type: 'text', text: `Error: ${message}` }],
59
- isError: true,
60
- };
61
- }
62
- });
63
- async function main() {
64
- const transport = new stdio_js_1.StdioServerTransport();
78
+ });
79
+ const transport = new StdioServerTransport();
65
80
  await server.connect(transport);
66
81
  }
67
- main().catch((err) => {
68
- console.error('Fatal:', err);
69
- process.exit(1);
70
- });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brandguideai-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "brandguide/AI MCP server — brand strategy knowledge base for Claude Code, Cursor, and any MCP client",
5
5
  "main": "dist/index.js",
6
6
  "bin": {