parisinnov-mcp 1.0.0 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +35 -6
  2. package/dist/index.js +63 -44
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # parisinnov-mcp
2
2
 
3
- MCP Server for RAG-powered knowledge base with **GLM-4.7**.
3
+ MCP Server for **Paris Innov** knowledge base powered by **GLM-4.7**.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -15,7 +15,7 @@ Add to `~/.claude/claude_desktop_config.json`:
15
15
  "command": "npx",
16
16
  "args": [
17
17
  "parisinnov-mcp",
18
- "--access-token", "your_api_key"
18
+ "--access-token", "YOUR_MCP_TOKEN"
19
19
  ]
20
20
  }
21
21
  }
@@ -30,20 +30,49 @@ Same config format in Cursor MCP settings.
30
30
 
31
31
  ## That's it!
32
32
 
33
- No installation needed. `npx` handles everything.
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
34
40
 
35
41
  ## Tools
36
42
 
37
43
  | Tool | Description |
38
44
  |------|-------------|
39
45
  | `search-context` | Search knowledge base with AI answers (GLM-4.7) |
40
- | `add-knowledge` | Add document to knowledge base |
46
+ | `add-knowledge` | Add document (auto-chunked & vectorized) |
41
47
  | `list-knowledge` | List all documents |
42
48
  | `delete-knowledge` | Delete a document |
43
49
 
44
- ## Get your API key
50
+ ## Architecture
45
51
 
46
- Contact the Paris Innov team to get your access token.
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
+ ```
47
76
 
48
77
  ## License
49
78
 
package/dist/index.js CHANGED
@@ -6,51 +6,61 @@ import axios from 'axios';
6
6
  import yargs from 'yargs';
7
7
  import { hideBin } from 'yargs/helpers';
8
8
  // ============================================================================
9
- // Configuration
9
+ // Configuration - Direct Supabase Edge Functions
10
10
  // ============================================================================
11
- const BACKEND_URL = 'https://parisinnovbackend-production.up.railway.app';
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';
12
14
  function parseArguments() {
13
15
  const argv = yargs(hideBin(process.argv))
14
16
  .option('access-token', {
15
17
  alias: 't',
16
18
  type: 'string',
17
- description: 'API key for backend authentication',
19
+ description: 'Your MCP token for authentication',
18
20
  demandOption: true
19
21
  })
20
22
  .help()
21
23
  .parseSync();
22
24
  return {
23
- accessToken: argv['access-token']
25
+ mcpToken: argv['access-token']
24
26
  };
25
27
  }
26
28
  // ============================================================================
27
- // Backend API Client
29
+ // Supabase Edge Functions Client
28
30
  // ============================================================================
29
- class BackendClient {
30
- accessToken;
31
- constructor(accessToken) {
32
- this.accessToken = accessToken;
31
+ class SupabaseClient {
32
+ mcpToken;
33
+ constructor(mcpToken) {
34
+ this.mcpToken = mcpToken;
33
35
  }
34
36
  get headers() {
35
37
  return {
36
- 'Authorization': `Bearer ${this.accessToken}`,
38
+ 'Authorization': `Bearer ${SUPABASE_ANON_KEY}`,
39
+ 'apikey': SUPABASE_ANON_KEY,
40
+ 'x-mcp-token': this.mcpToken,
37
41
  'Content-Type': 'application/json'
38
42
  };
39
43
  }
40
- async ask(question) {
41
- const response = await axios.post(`${BACKEND_URL}/ask`, { question }, { headers: this.headers, timeout: 60000 });
44
+ async search(question) {
45
+ const response = await axios.post(`${EDGE_FUNCTIONS_URL}/search-context`, { question, match_count: 5, match_threshold: 0.5 }, { headers: this.headers, timeout: 60000 });
42
46
  return response.data;
43
47
  }
44
- async addDocument(content, title, source) {
45
- const response = await axios.post(`${BACKEND_URL}/documents`, { content, title, source }, { headers: this.headers, timeout: 30000 });
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, timeout: 60000 });
46
56
  return response.data;
47
57
  }
48
58
  async listDocuments(limit = 20) {
49
- const response = await axios.get(`${BACKEND_URL}/documents?limit=${limit}`, { headers: this.headers, timeout: 30000 });
59
+ const response = await axios.get(`${EDGE_FUNCTIONS_URL}/list-documents?limit=${limit}`, { headers: this.headers, timeout: 30000 });
50
60
  return response.data;
51
61
  }
52
- async deleteDocument(id) {
53
- const response = await axios.delete(`${BACKEND_URL}/documents/${id}`, { headers: this.headers, timeout: 30000 });
62
+ async deleteDocument(documentId) {
63
+ const response = await axios.post(`${EDGE_FUNCTIONS_URL}/delete-document`, { document_id: documentId }, { headers: this.headers, timeout: 30000 });
54
64
  return response.data;
55
65
  }
56
66
  }
@@ -59,10 +69,12 @@ class BackendClient {
59
69
  // ============================================================================
60
70
  async function startMCPServer() {
61
71
  const args = parseArguments();
62
- const backend = new BackendClient(args.accessToken);
72
+ const supabase = new SupabaseClient(args.mcpToken);
73
+ // Cache org_id after first call
74
+ let cachedOrgId = null;
63
75
  const server = new Server({
64
76
  name: 'parisinnov-mcp',
65
- version: '1.0.0',
77
+ version: '1.1.0',
66
78
  }, {
67
79
  capabilities: { tools: {} },
68
80
  });
@@ -71,7 +83,7 @@ async function startMCPServer() {
71
83
  tools: [
72
84
  {
73
85
  name: 'search-context',
74
- description: 'Search your knowledge base and get AI-powered answers using GLM-4.7.',
86
+ description: 'Search your organization\'s knowledge base and get AI-powered answers using GLM-4.7.',
75
87
  inputSchema: {
76
88
  type: 'object',
77
89
  properties: {
@@ -85,21 +97,17 @@ async function startMCPServer() {
85
97
  },
86
98
  {
87
99
  name: 'add-knowledge',
88
- description: 'Add new content to your knowledge base. It will be vectorized and available for future searches.',
100
+ description: 'Add new content to your organization\'s knowledge base. Content is automatically chunked and vectorized.',
89
101
  inputSchema: {
90
102
  type: 'object',
91
103
  properties: {
92
104
  content: {
93
105
  type: 'string',
94
- description: 'The content to add'
106
+ description: 'The content to add (supports markdown)'
95
107
  },
96
108
  title: {
97
109
  type: 'string',
98
- description: 'Optional title for the content'
99
- },
100
- source: {
101
- type: 'string',
102
- description: 'Optional source identifier (e.g., "slack", "notion", "manual")'
110
+ description: 'Title for the document'
103
111
  }
104
112
  },
105
113
  required: ['content']
@@ -107,7 +115,7 @@ async function startMCPServer() {
107
115
  },
108
116
  {
109
117
  name: 'list-knowledge',
110
- description: 'List documents in your knowledge base',
118
+ description: 'List documents in your organization\'s knowledge base',
111
119
  inputSchema: {
112
120
  type: 'object',
113
121
  properties: {
@@ -120,7 +128,7 @@ async function startMCPServer() {
120
128
  },
121
129
  {
122
130
  name: 'delete-knowledge',
123
- description: 'Delete a document from your knowledge base',
131
+ description: 'Delete a document from your organization\'s knowledge base',
124
132
  inputSchema: {
125
133
  type: 'object',
126
134
  properties: {
@@ -144,35 +152,41 @@ async function startMCPServer() {
144
152
  if (!question || typeof question !== 'string') {
145
153
  throw new Error('Question is required');
146
154
  }
147
- const result = await backend.ask(question);
155
+ const result = await supabase.search(question);
148
156
  let responseText = result.answer;
149
- if (result.sources.length > 0) {
157
+ if (result.sources && result.sources.length > 0) {
150
158
  responseText += '\n\n---\n**Sources:**';
151
159
  result.sources.forEach((source, i) => {
152
- responseText += `\n${i + 1}. ${source.title || 'Document'} (${(source.similarity * 100).toFixed(0)}%)`;
160
+ responseText += `\n${i + 1}. ${source.title} (${(source.similarity * 100).toFixed(0)}%)`;
153
161
  });
154
162
  }
155
- responseText += `\n\n_${result.metadata.documents_found} docs | ${result.metadata.processing_time_ms}ms | ${result.metadata.model}_`;
163
+ responseText += `\n\n_${result.metadata.documents_found} chunks | ${result.metadata.processing_time_ms}ms | ${result.metadata.model}_`;
156
164
  return {
157
165
  content: [{ type: 'text', text: responseText }]
158
166
  };
159
167
  }
160
168
  case 'add-knowledge': {
161
- const { content, title, source } = toolArgs;
169
+ const { content, title } = toolArgs;
162
170
  if (!content || typeof content !== 'string') {
163
171
  throw new Error('Content is required');
164
172
  }
165
- const result = await backend.addDocument(content, title, source);
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);
166
179
  return {
167
180
  content: [{
168
181
  type: 'text',
169
- text: `Document added!\nID: ${result.document.id}\nTitle: ${result.document.title}`
182
+ text: `Document added!\nID: ${result.documentId}\nChunks created: ${result.chunks}`
170
183
  }]
171
184
  };
172
185
  }
173
186
  case 'list-knowledge': {
174
187
  const { limit } = toolArgs;
175
- const result = await backend.listDocuments(limit || 20);
188
+ const result = await supabase.listDocuments(limit || 20);
189
+ cachedOrgId = result.org_id; // Cache for future add-knowledge calls
176
190
  if (result.documents.length === 0) {
177
191
  return {
178
192
  content: [{ type: 'text', text: 'No documents in knowledge base.' }]
@@ -192,7 +206,7 @@ async function startMCPServer() {
192
206
  if (!id) {
193
207
  throw new Error('Document ID is required');
194
208
  }
195
- const result = await backend.deleteDocument(id);
209
+ const result = await supabase.deleteDocument(id);
196
210
  return {
197
211
  content: [{
198
212
  type: 'text',
@@ -209,17 +223,22 @@ async function startMCPServer() {
209
223
  catch (error) {
210
224
  const message = error instanceof Error ? error.message : 'Unknown error';
211
225
  if (axios.isAxiosError(error)) {
212
- if (error.response) {
226
+ const status = error.response?.status;
227
+ const errorMsg = error.response?.data?.error || error.message;
228
+ if (status === 401) {
213
229
  return {
214
- content: [{
215
- type: 'text',
216
- text: `Backend error (${error.response.status}): ${error.response.data?.message || error.message}`
217
- }],
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}` }],
218
237
  isError: true
219
238
  };
220
239
  }
221
240
  return {
222
- content: [{ type: 'text', text: `Network error: ${message}` }],
241
+ content: [{ type: 'text', text: `Error (${status}): ${errorMsg}` }],
223
242
  isError: true
224
243
  };
225
244
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parisinnov-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "MCP Server with Smart Query Builder (GLM-4.7) for RAG-powered knowledge base",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",