brief-mcp 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,79 @@
1
+ # parisinnov-mcp
2
+
3
+ MCP Server for **Paris Innov** knowledge base powered by **GLM-4.7**.
4
+
5
+ ## Quick Start
6
+
7
+ ### Claude Code
8
+
9
+ Add to `~/.claude/claude_desktop_config.json`:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "parisinnov": {
15
+ "command": "npx",
16
+ "args": [
17
+ "parisinnov-mcp",
18
+ "--access-token", "YOUR_MCP_TOKEN"
19
+ ]
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
26
+
27
+ ### Cursor
28
+
29
+ Same config format in Cursor MCP settings.
30
+
31
+ ## That's it!
32
+
33
+ No backend needed. The MCP connects directly to Supabase Edge Functions.
34
+
35
+ ## Get your MCP Token
36
+
37
+ 1. Sign up at Paris Innov
38
+ 2. Join an organization
39
+ 3. Copy your MCP token from your profile
40
+
41
+ ## Tools
42
+
43
+ | Tool | Description |
44
+ |------|-------------|
45
+ | `search-context` | Search knowledge base with AI answers (GLM-4.7) |
46
+ | `add-knowledge` | Add document (auto-chunked & vectorized) |
47
+ | `list-knowledge` | List all documents |
48
+ | `delete-knowledge` | Delete a document |
49
+
50
+ ## Architecture
51
+
52
+ ```
53
+ Claude Code / Cursor
54
+
55
+
56
+ ┌──────────────────────────┐
57
+ │ parisinnov-mcp (npm) │
58
+ └──────────┬───────────────┘
59
+ │ Direct HTTPS
60
+
61
+ ┌──────────────────────────┐
62
+ │ Supabase Edge Functions │
63
+ │ • search-context │
64
+ │ • ingest-context │
65
+ │ • list-documents │
66
+ │ • delete-document │
67
+ └──────────┬───────────────┘
68
+
69
+
70
+ ┌──────────────────────────┐
71
+ │ Supabase PostgreSQL │
72
+ │ • pgvector embeddings │
73
+ │ • Multi-tenant (orgs) │
74
+ └──────────────────────────┘
75
+ ```
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import axios from 'axios';
6
+ import yargs from 'yargs';
7
+ import { hideBin } from 'yargs/helpers';
8
+ // ============================================================================
9
+ // Configuration - Direct Supabase Edge Functions
10
+ // ============================================================================
11
+ const SUPABASE_URL = 'https://pubrvnwqeimnoxgdeage.supabase.co';
12
+ const EDGE_FUNCTIONS_URL = `${SUPABASE_URL}/functions/v1`;
13
+ const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB1YnJ2bndxZWltbm94Z2RlYWdlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njg2MjU3MTQsImV4cCI6MjA4NDIwMTcxNH0.LdMqYS0quwLdSXkTKEJyYCghSRzrYNNSCkv-tUtGUy4';
14
+ function parseArguments() {
15
+ const argv = yargs(hideBin(process.argv))
16
+ .option('access-token', {
17
+ alias: 't',
18
+ type: 'string',
19
+ description: 'Your MCP token for authentication',
20
+ demandOption: true
21
+ })
22
+ .help()
23
+ .parseSync();
24
+ return {
25
+ mcpToken: argv['access-token']
26
+ };
27
+ }
28
+ // ============================================================================
29
+ // Supabase Edge Functions Client
30
+ // ============================================================================
31
+ class SupabaseClient {
32
+ mcpToken;
33
+ constructor(mcpToken) {
34
+ this.mcpToken = mcpToken;
35
+ }
36
+ get headers() {
37
+ return {
38
+ 'Authorization': `Bearer ${SUPABASE_ANON_KEY}`,
39
+ 'apikey': SUPABASE_ANON_KEY,
40
+ 'x-mcp-token': this.mcpToken,
41
+ 'Content-Type': 'application/json'
42
+ };
43
+ }
44
+ async search(question) {
45
+ const response = await axios.post(`${EDGE_FUNCTIONS_URL}/search-context`, { question, match_count: 5, match_threshold: 0.3 }, { headers: this.headers });
46
+ return response.data;
47
+ }
48
+ async ingestDocument(content, title, orgId) {
49
+ // First get user's org_id from a simple call
50
+ const response = await axios.post(`${EDGE_FUNCTIONS_URL}/ingest-context`, {
51
+ content,
52
+ title: title || 'Untitled',
53
+ org_id: orgId,
54
+ source_type: 'mcp'
55
+ }, { headers: this.headers });
56
+ return response.data;
57
+ }
58
+ async listDocuments(limit = 20) {
59
+ const response = await axios.get(`${EDGE_FUNCTIONS_URL}/list-documents?limit=${limit}`, { headers: this.headers });
60
+ return response.data;
61
+ }
62
+ async deleteDocument(documentId) {
63
+ const response = await axios.post(`${EDGE_FUNCTIONS_URL}/delete-document`, { document_id: documentId }, { headers: this.headers });
64
+ return response.data;
65
+ }
66
+ }
67
+ // ============================================================================
68
+ // MCP Server
69
+ // ============================================================================
70
+ async function startMCPServer() {
71
+ const args = parseArguments();
72
+ const supabase = new SupabaseClient(args.mcpToken);
73
+ // Cache org_id after first call
74
+ let cachedOrgId = null;
75
+ const server = new Server({
76
+ name: 'parisinnov-mcp',
77
+ version: '1.1.0',
78
+ }, {
79
+ capabilities: { tools: {} },
80
+ });
81
+ // List available tools
82
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
83
+ tools: [
84
+ {
85
+ name: 'search-context',
86
+ description: 'Search your organization\'s knowledge base and get AI-powered answers using GLM-4.7.',
87
+ inputSchema: {
88
+ type: 'object',
89
+ properties: {
90
+ question: {
91
+ type: 'string',
92
+ description: 'Your question or search query'
93
+ }
94
+ },
95
+ required: ['question']
96
+ }
97
+ },
98
+ {
99
+ name: 'add-knowledge',
100
+ description: 'Add new content to your organization\'s knowledge base. Content is automatically chunked and vectorized.',
101
+ inputSchema: {
102
+ type: 'object',
103
+ properties: {
104
+ content: {
105
+ type: 'string',
106
+ description: 'The content to add (supports markdown)'
107
+ },
108
+ title: {
109
+ type: 'string',
110
+ description: 'Title for the document'
111
+ }
112
+ },
113
+ required: ['content']
114
+ }
115
+ },
116
+ {
117
+ name: 'list-knowledge',
118
+ description: 'List documents in your organization\'s knowledge base',
119
+ inputSchema: {
120
+ type: 'object',
121
+ properties: {
122
+ limit: {
123
+ type: 'number',
124
+ description: 'Maximum number of documents to return (default: 20)'
125
+ }
126
+ }
127
+ }
128
+ },
129
+ {
130
+ name: 'delete-knowledge',
131
+ description: 'Delete a document from your organization\'s knowledge base',
132
+ inputSchema: {
133
+ type: 'object',
134
+ properties: {
135
+ id: {
136
+ type: 'string',
137
+ description: 'The document ID to delete'
138
+ }
139
+ },
140
+ required: ['id']
141
+ }
142
+ }
143
+ ]
144
+ }));
145
+ // Handle tool calls
146
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
147
+ const { name, arguments: toolArgs } = request.params;
148
+ try {
149
+ switch (name) {
150
+ case 'search-context': {
151
+ const { question } = toolArgs;
152
+ if (!question || typeof question !== 'string') {
153
+ throw new Error('Question is required');
154
+ }
155
+ const result = await supabase.search(question);
156
+ let responseText = result.answer;
157
+ if (result.sources && result.sources.length > 0) {
158
+ responseText += '\n\n---\n**Sources:**';
159
+ result.sources.forEach((source, i) => {
160
+ responseText += `\n${i + 1}. ${source.title} (${(source.similarity * 100).toFixed(0)}%)`;
161
+ });
162
+ }
163
+ responseText += `\n\n_${result.metadata.documents_found} chunks | ${result.metadata.processing_time_ms}ms | ${result.metadata.model}_`;
164
+ return {
165
+ content: [{ type: 'text', text: responseText }]
166
+ };
167
+ }
168
+ case 'add-knowledge': {
169
+ const { content, title } = toolArgs;
170
+ if (!content || typeof content !== 'string') {
171
+ throw new Error('Content is required');
172
+ }
173
+ // Get org_id from list-documents if not cached
174
+ if (!cachedOrgId) {
175
+ const listResult = await supabase.listDocuments(1);
176
+ cachedOrgId = listResult.org_id;
177
+ }
178
+ const result = await supabase.ingestDocument(content, title, cachedOrgId);
179
+ return {
180
+ content: [{
181
+ type: 'text',
182
+ text: `Document added!\nID: ${result.documentId}\nChunks created: ${result.chunks}`
183
+ }]
184
+ };
185
+ }
186
+ case 'list-knowledge': {
187
+ const { limit } = toolArgs;
188
+ const result = await supabase.listDocuments(limit || 20);
189
+ cachedOrgId = result.org_id; // Cache for future add-knowledge calls
190
+ if (result.documents.length === 0) {
191
+ return {
192
+ content: [{ type: 'text', text: 'No documents in knowledge base.' }]
193
+ };
194
+ }
195
+ let text = `**${result.documents.length} document(s):**\n\n`;
196
+ result.documents.forEach((doc, i) => {
197
+ text += `${i + 1}. **${doc.title}** (ID: \`${doc.id}\`)\n`;
198
+ text += ` ${doc.snippet}\n\n`;
199
+ });
200
+ return {
201
+ content: [{ type: 'text', text }]
202
+ };
203
+ }
204
+ case 'delete-knowledge': {
205
+ const { id } = toolArgs;
206
+ if (!id) {
207
+ throw new Error('Document ID is required');
208
+ }
209
+ const result = await supabase.deleteDocument(id);
210
+ return {
211
+ content: [{
212
+ type: 'text',
213
+ text: result.success
214
+ ? `Document ${id} deleted.`
215
+ : `Failed to delete document ${id}.`
216
+ }]
217
+ };
218
+ }
219
+ default:
220
+ throw new Error(`Unknown tool: ${name}`);
221
+ }
222
+ }
223
+ catch (error) {
224
+ const message = error instanceof Error ? error.message : 'Unknown error';
225
+ if (axios.isAxiosError(error)) {
226
+ const status = error.response?.status;
227
+ const errorMsg = error.response?.data?.error || error.message;
228
+ if (status === 401) {
229
+ return {
230
+ content: [{ type: 'text', text: 'Invalid MCP token. Check your access-token.' }],
231
+ isError: true
232
+ };
233
+ }
234
+ if (status === 400) {
235
+ return {
236
+ content: [{ type: 'text', text: `Bad request: ${errorMsg}` }],
237
+ isError: true
238
+ };
239
+ }
240
+ return {
241
+ content: [{ type: 'text', text: `Error (${status}): ${errorMsg}` }],
242
+ isError: true
243
+ };
244
+ }
245
+ return {
246
+ content: [{ type: 'text', text: `Error: ${message}` }],
247
+ isError: true
248
+ };
249
+ }
250
+ });
251
+ // Start server
252
+ const transport = new StdioServerTransport();
253
+ await server.connect(transport);
254
+ }
255
+ // Error handlers
256
+ process.on('uncaughtException', (error) => {
257
+ console.error('Uncaught exception:', error);
258
+ process.exit(1);
259
+ });
260
+ process.on('unhandledRejection', (reason) => {
261
+ console.error('Unhandled rejection:', reason);
262
+ process.exit(1);
263
+ });
264
+ // Start
265
+ startMCPServer().catch((error) => {
266
+ console.error('Failed to start MCP server:', error);
267
+ process.exit(1);
268
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "brief-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Brief MCP - Context-first development with RAG-powered knowledge base",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "brief-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "ts-node --esm src/index.ts",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": ["mcp", "model-context-protocol", "rag", "ai", "claude", "cursor", "glm", "featherless", "knowledge-base"],
17
+ "author": "Brief Team - Paris Innov Hackathon",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/marlowetal653/innovhack.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/marlowetal653/innovhack/issues"
25
+ },
26
+ "homepage": "https://github.com/marlowetal653/innovhack#readme",
27
+ "files": ["dist/", "README.md"],
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^0.5.0",
30
+ "axios": "^1.6.0",
31
+ "yargs": "^17.7.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20.0.0",
35
+ "@types/yargs": "^17.0.0",
36
+ "ts-node": "^10.9.0",
37
+ "typescript": "^5.0.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ }
42
+ }