qeek-mcp-beta 1.0.0

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 ADDED
@@ -0,0 +1,128 @@
1
+ # Qeek MCP
2
+
3
+ πŸ“‹ **Access your Qeek briefs directly in Cursor and Windsurf**
4
+
5
+ Say "work on brief id: xxx" and instantly load all your product specs, technical specs, UI mockups, and diagrams into your AI assistant.
6
+
7
+ ## πŸš€ Quick Start
8
+
9
+ ### 1. Install & Setup
10
+
11
+ ```bash
12
+ npx qeek-mcp setup
13
+ ```
14
+
15
+ This will:
16
+ - Prompt you for your Qeek API token
17
+ - Save configuration to `~/.qeek/config.json`
18
+ - Auto-configure Cursor (`~/.cursor/mcp.json`)
19
+ - Auto-configure Windsurf (`~/.codeium/windsurf/mcp_config.json`)
20
+
21
+ ### 2. Get Your Token
22
+
23
+ 1. Go to https://app.qeek.ai/settings/api-tokens
24
+ 2. Generate a new token (starts with `qek_live_`)
25
+ 3. Paste it during setup
26
+
27
+ ### 3. Use It
28
+
29
+ In Cursor or Windsurf, simply type:
30
+
31
+ ```
32
+ work on brief id: architect-1234567890-abcdef
33
+ ```
34
+
35
+ The MCP tool will automatically load all specs from that brief as context.
36
+
37
+ ## 🎯 How It Works
38
+
39
+ ```
40
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
41
+ β”‚ Cursor IDE │◄────►│ qeek-mcp │◄────►│ api.qeek.ai β”‚
42
+ β”‚ or Windsurf β”‚stdio β”‚ (npx) β”‚HTTPS β”‚ /internal/briefs β”‚
43
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
44
+ ```
45
+
46
+ ## πŸ“‹ Features
47
+
48
+ - **One-command context loading** β€” All specs from your brief
49
+ - **Multi-type support** β€” Product, technical, UI mockups, diagrams
50
+ - **Smart formatting** β€” Organized markdown with clear sections
51
+ - **IDE integration** β€” Works with Cursor and Windsurf
52
+ - **Token-based auth** β€” Simple, secure, no OAuth redirects
53
+
54
+ ## πŸ”§ Manual Configuration
55
+
56
+ If automatic setup doesn't work, manually add to your MCP config:
57
+
58
+ ### Cursor (`~/.cursor/mcp.json`)
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "qeek": {
64
+ "command": "npx",
65
+ "args": ["-y", "qeek-mcp"]
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### Windsurf (`~/.codeium/windsurf/mcp_config.json`)
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "qeek": {
77
+ "command": "npx",
78
+ "args": ["-y", "qeek-mcp"]
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## πŸ“ Usage Examples
85
+
86
+ **Load a brief:**
87
+ ```
88
+ work on brief id: architect-1234567890-abcdef
89
+ ```
90
+
91
+ **With additional context:**
92
+ ```
93
+ work on brief id: architect-1234567890-abcdef, I need to implement the authentication flow
94
+ ```
95
+
96
+ ## πŸ› Troubleshooting
97
+
98
+ ### "Qeek MCP not configured"
99
+
100
+ Run setup again:
101
+ ```bash
102
+ npx qeek-mcp setup
103
+ ```
104
+
105
+ ### "Invalid API token"
106
+
107
+ 1. Go to https://app.qeek.ai/settings/api-tokens
108
+ 2. Generate a new token
109
+ 3. Re-run setup
110
+
111
+ ### "Brief not found"
112
+
113
+ - Verify the brief ID is correct
114
+ - Ensure you have access to that brief in the Qeek UI
115
+
116
+ ## πŸ”’ Security
117
+
118
+ - Token stored only in `~/.qeek/config.json` on your machine
119
+ - Never transmitted except to official Qeek API
120
+ - Token can be revoked at any time in Qeek UI
121
+
122
+ ## πŸ“„ License
123
+
124
+ MIT License - see LICENSE for details.
125
+
126
+ ---
127
+
128
+ **Built with ❀️ by the Qeek team**
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "qeek-mcp-beta",
3
+ "version": "1.0.0",
4
+ "description": "Qeek MCP server for Cursor and Windsurf - Access your briefs directly in your IDE",
5
+ "main": "src/server.js",
6
+ "bin": {
7
+ "qeek-mcp": "./src/server.js",
8
+ "qeek-mcp-setup": "./src/cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/server.js",
12
+ "setup": "node src/cli.js"
13
+ },
14
+ "keywords": ["mcp", "cursor", "windsurf", "qeek", "briefs", "specs"],
15
+ "author": "Qeek",
16
+ "license": "MIT",
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.0.0"
19
+ },
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ }
23
+ }
package/src/cli.js ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ const readline = require('readline');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const { saveConfig } = require('./config');
7
+
8
+ const rl = readline.createInterface({
9
+ input: process.stdin,
10
+ output: process.stdout
11
+ });
12
+
13
+ async function setup() {
14
+ console.log('\nπŸ”‘ Qeek MCP Setup');
15
+ console.log('==================\n');
16
+ console.log('1. Go to: https://app.qeek.ai/settings/api-tokens');
17
+ console.log('2. Generate a new token');
18
+ console.log('3. Paste it below:\n');
19
+
20
+ const token = await new Promise(resolve => {
21
+ rl.question('Token: ', resolve);
22
+ });
23
+
24
+ if (!token.startsWith('qek_live_')) {
25
+ console.log('\n⚠️ Warning: Token should start with "qek_live_"');
26
+ }
27
+
28
+ // Save config
29
+ saveConfig({
30
+ version: 1,
31
+ token: token.trim(),
32
+ apiUrl: 'https://api.qeek.ai',
33
+ createdAt: new Date().toISOString()
34
+ });
35
+
36
+ // Configure Cursor
37
+ await configureCursor();
38
+
39
+ // Configure Windsurf
40
+ await configureWindsurf();
41
+
42
+ console.log('\nβœ… Setup complete!');
43
+ console.log('Use: "work on brief id: <brief-id>" in Cursor/Windsurf\n');
44
+
45
+ rl.close();
46
+ }
47
+
48
+ async function configureCursor() {
49
+ const cursorDir = path.join(os.homedir(), '.cursor');
50
+ const mcpConfigPath = path.join(cursorDir, 'mcp.json');
51
+
52
+ let config = {};
53
+ if (fs.existsSync(mcpConfigPath)) {
54
+ config = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
55
+ }
56
+
57
+ config.mcpServers = config.mcpServers || {};
58
+ config.mcpServers.qeek = {
59
+ command: 'npx',
60
+ args: ['-y', 'qeek-mcp']
61
+ };
62
+
63
+ if (!fs.existsSync(cursorDir)) {
64
+ fs.mkdirSync(cursorDir, { recursive: true });
65
+ }
66
+
67
+ fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
68
+ console.log('βœ“ Cursor MCP config updated');
69
+ }
70
+
71
+ async function configureWindsurf() {
72
+ const windsurfDir = path.join(os.homedir(), '.codeium', 'windsurf');
73
+ const mcpConfigPath = path.join(windsurfDir, 'mcp_config.json');
74
+
75
+ let config = {};
76
+ if (fs.existsSync(mcpConfigPath)) {
77
+ config = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
78
+ }
79
+
80
+ config.mcpServers = config.mcpServers || {};
81
+ config.mcpServers.qeek = {
82
+ command: 'npx',
83
+ args: ['-y', 'qeek-mcp']
84
+ };
85
+
86
+ if (!fs.existsSync(windsurfDir)) {
87
+ fs.mkdirSync(windsurfDir, { recursive: true });
88
+ }
89
+
90
+ fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
91
+ console.log('βœ“ Windsurf MCP config updated');
92
+ }
93
+
94
+ if (require.main === module) {
95
+ setup().catch(console.error);
96
+ }
97
+
98
+ module.exports = { setup };
package/src/config.js ADDED
@@ -0,0 +1,40 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.qeek');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+
8
+ function loadConfig() {
9
+ if (!fs.existsSync(CONFIG_FILE)) {
10
+ throw new Error(
11
+ 'Qeek MCP not configured. Run: npx qeek-mcp setup\n' +
12
+ 'Get your token at: https://app.qeek.ai/settings/api-tokens'
13
+ );
14
+ }
15
+
16
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
17
+
18
+ return {
19
+ token: config.token,
20
+ apiUrl: config.apiUrl || 'https://api.qeek.ai'
21
+ };
22
+ }
23
+
24
+ function saveConfig(config) {
25
+ if (!fs.existsSync(CONFIG_DIR)) {
26
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
27
+ }
28
+
29
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
30
+
31
+ // Ensure permissions even if umask interfered
32
+ try {
33
+ fs.chmodSync(CONFIG_FILE, 0o600);
34
+ fs.chmodSync(CONFIG_DIR, 0o700);
35
+ } catch (e) {
36
+ // Ignore permission errors on Windows or restricted environments
37
+ }
38
+ }
39
+
40
+ module.exports = { loadConfig, saveConfig, CONFIG_DIR };
package/src/server.js ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Qeek MCP Server - Brief Context Tool
5
+ *
6
+ * This script creates an MCP-compatible server that provides the getBriefContext tool
7
+ * for loading Qeek brief specs into Cursor/Windsurf.
8
+ */
9
+
10
+ // Handle setup command
11
+ if (process.argv[2] === 'setup') {
12
+ const { setup } = require('./cli.js');
13
+ setup().then(() => process.exit(0)).catch(err => {
14
+ console.error('Setup failed:', err);
15
+ process.exit(1);
16
+ });
17
+ return; // Prevent falling through to server startup
18
+ }
19
+
20
+ const { loadConfig } = require('./config');
21
+ const { getBriefContext } = require('./tools/getBriefContext');
22
+
23
+ const config = loadConfig();
24
+
25
+ const tools = [
26
+ {
27
+ name: 'getBriefContext',
28
+ description: 'πŸ“‹ Load all specs (product, tech, UI mockups, diagrams) from a Qeek brief. Use when user says "work on brief id: xxx" or "load brief xxx".',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ briefId: {
33
+ type: 'string',
34
+ description: 'The brief/chat session ID (e.g., "architect-1234567890-abcdef")'
35
+ }
36
+ },
37
+ required: ['briefId']
38
+ }
39
+ }
40
+ ];
41
+
42
+ function sendResponse(response) {
43
+ console.log(JSON.stringify(response));
44
+ }
45
+
46
+ async function handleRequest(request) {
47
+ try {
48
+ const parsed = typeof request === 'string' ? JSON.parse(request) : request;
49
+
50
+ // Handle notifications (no id field) - don't send response
51
+ if (parsed.id === undefined) {
52
+ // Log notifications for debugging but don't respond
53
+ if (parsed.method?.startsWith('notifications/')) {
54
+ console.error(`πŸ“¨ Notification: ${parsed.method}`);
55
+ }
56
+ return;
57
+ }
58
+
59
+ switch (parsed.method) {
60
+ case 'initialize':
61
+ sendResponse({
62
+ jsonrpc: '2.0',
63
+ id: parsed.id,
64
+ result: {
65
+ protocolVersion: '2024-11-05',
66
+ capabilities: {
67
+ tools: {}
68
+ },
69
+ serverInfo: {
70
+ name: 'qeek-mcp',
71
+ version: '1.0.0',
72
+ description: 'πŸ“‹ Load Qeek brief specs into your IDE'
73
+ }
74
+ }
75
+ });
76
+ break;
77
+
78
+ case 'tools/list':
79
+ sendResponse({
80
+ jsonrpc: '2.0',
81
+ id: parsed.id,
82
+ result: { tools }
83
+ });
84
+ break;
85
+
86
+ case 'tools/call':
87
+ const { name, arguments: args } = parsed.params;
88
+
89
+ if (name === 'getBriefContext') {
90
+ const result = await getBriefContext(args.briefId, config);
91
+ sendResponse({
92
+ jsonrpc: '2.0',
93
+ id: parsed.id,
94
+ result
95
+ });
96
+ } else {
97
+ sendResponse({
98
+ jsonrpc: '2.0',
99
+ id: parsed.id,
100
+ error: {
101
+ code: -32601,
102
+ message: `Unknown tool: ${name}`
103
+ }
104
+ });
105
+ }
106
+ break;
107
+
108
+ default:
109
+ sendResponse({
110
+ jsonrpc: '2.0',
111
+ id: parsed.id,
112
+ error: {
113
+ code: -32601,
114
+ message: `Unknown method: ${parsed.method}`
115
+ }
116
+ });
117
+ }
118
+ } catch (error) {
119
+ sendResponse({
120
+ jsonrpc: '2.0',
121
+ error: {
122
+ code: -32700,
123
+ message: 'Parse error',
124
+ data: error.message
125
+ }
126
+ });
127
+ }
128
+ }
129
+
130
+ function start() {
131
+ console.error('πŸš€ Qeek MCP Server starting...');
132
+ console.error('πŸ“‹ Tool: getBriefContext');
133
+ console.error('πŸ“‘ MCP Server ready - listening for requests...');
134
+
135
+ process.stdin.setEncoding('utf8');
136
+
137
+ // Buffer for handling partial messages
138
+ let buffer = '';
139
+
140
+ process.stdin.on('data', (data) => {
141
+ buffer += data;
142
+
143
+ // Process complete lines from buffer
144
+ let newlineIndex;
145
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
146
+ const line = buffer.substring(0, newlineIndex).trim();
147
+ buffer = buffer.substring(newlineIndex + 1);
148
+
149
+ if (line) {
150
+ handleRequest(line);
151
+ }
152
+ }
153
+ });
154
+
155
+ process.stdin.on('end', () => {
156
+ // Process any remaining data in buffer
157
+ const remaining = buffer.trim();
158
+ if (remaining) {
159
+ handleRequest(remaining);
160
+ }
161
+ process.exit(0);
162
+ });
163
+
164
+ process.on('SIGINT', () => process.exit(0));
165
+ process.on('SIGTERM', () => process.exit(0));
166
+ }
167
+
168
+ start();
169
+
@@ -0,0 +1,126 @@
1
+ async function getBriefContext(briefId, config) {
2
+ console.error(`πŸ“‹ Loading brief: ${briefId}`);
3
+
4
+ try {
5
+ const response = await fetch(`${config.apiUrl}/internal/briefs/${briefId}`, {
6
+ headers: {
7
+ 'Authorization': `Bearer ${config.token}`,
8
+ 'Content-Type': 'application/json'
9
+ }
10
+ });
11
+
12
+ if (response.status === 401) {
13
+ return {
14
+ content: [{
15
+ type: 'text',
16
+ text: '❌ Invalid API token.\n\nPlease run: `npx qeek-mcp setup`\nGet a new token at: https://app.qeek.ai/settings/api-tokens'
17
+ }],
18
+ isError: true
19
+ };
20
+ }
21
+
22
+ if (response.status === 404) {
23
+ return {
24
+ content: [{
25
+ type: 'text',
26
+ text: `❌ Brief not found: ${briefId}\n\nPlease verify the brief ID is correct.`
27
+ }],
28
+ isError: true
29
+ };
30
+ }
31
+
32
+ if (!response.ok) {
33
+ throw new Error(`API error: ${response.status}`);
34
+ }
35
+
36
+ const brief = await response.json();
37
+ const markdown = formatBriefAsMarkdown(brief);
38
+
39
+ console.error(`βœ… Loaded brief with ${brief.specs?.length || 0} specs`);
40
+
41
+ return {
42
+ content: [{ type: 'text', text: markdown }],
43
+ structuredContent: {
44
+ briefId: brief.id,
45
+ title: brief.title,
46
+ projectId: brief.projectId,
47
+ repositoryNames: brief.repositoryNames,
48
+ specCounts: countSpecsByType(brief.specs)
49
+ }
50
+ };
51
+
52
+ } catch (error) {
53
+ console.error('❌ Error:', error.message);
54
+ return {
55
+ content: [{
56
+ type: 'text',
57
+ text: `❌ Error loading brief: ${error.message}`
58
+ }],
59
+ isError: true
60
+ };
61
+ }
62
+ }
63
+
64
+ function formatBriefAsMarkdown(brief) {
65
+ let md = `# πŸ“‹ Brief: ${brief.title}\n\n`;
66
+ md += `**ID:** \`${brief.id}\`\n`;
67
+ md += `**Project:** ${brief.projectId}\n`;
68
+ md += `**Repos:** ${brief.repositoryNames?.join(', ') || 'None'}\n\n`;
69
+ md += `---\n\n`;
70
+
71
+ // Group and format specs
72
+ const specs = brief.specs || [];
73
+
74
+ const productSpecs = specs.filter(s => s.specType === 'product');
75
+ const techSpecs = specs.filter(s => s.specType === 'tech');
76
+ const uiSpecs = specs.filter(s => s.specType === 'ui');
77
+ const diagramSpecs = specs.filter(s => s.specType === 'diagram');
78
+ const customSpecs = specs.filter(s => s.specType === 'custom');
79
+
80
+ if (productSpecs.length) {
81
+ md += `## πŸ“– Product Specs\n\n`;
82
+ productSpecs.forEach(s => {
83
+ md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
84
+ });
85
+ }
86
+
87
+ if (techSpecs.length) {
88
+ md += `## βš™οΈ Technical Specs\n\n`;
89
+ techSpecs.forEach(s => {
90
+ md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
91
+ });
92
+ }
93
+
94
+ if (uiSpecs.length) {
95
+ md += `## 🎨 UI Mockups\n\n`;
96
+ uiSpecs.forEach(s => {
97
+ md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
98
+ });
99
+ }
100
+
101
+ if (diagramSpecs.length) {
102
+ md += `## πŸ“Š Diagrams\n\n`;
103
+ diagramSpecs.forEach(s => {
104
+ md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
105
+ });
106
+ }
107
+
108
+ if (customSpecs.length) {
109
+ md += `## πŸ“ Custom Specs\n\n`;
110
+ customSpecs.forEach(s => {
111
+ md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
112
+ });
113
+ }
114
+
115
+ return md;
116
+ }
117
+
118
+ function countSpecsByType(specs) {
119
+ const counts = { product: 0, tech: 0, ui: 0, diagram: 0, custom: 0 };
120
+ (specs || []).forEach(s => {
121
+ counts[s.specType] = (counts[s.specType] || 0) + 1;
122
+ });
123
+ return counts;
124
+ }
125
+
126
+ module.exports = { getBriefContext };