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.
- package/README.md +35 -6
- package/dist/index.js +63 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# parisinnov-mcp
|
|
2
2
|
|
|
3
|
-
MCP Server for
|
|
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", "
|
|
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
|
|
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
|
|
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
|
-
##
|
|
50
|
+
## Architecture
|
|
45
51
|
|
|
46
|
-
|
|
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
|
|
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: '
|
|
19
|
+
description: 'Your MCP token for authentication',
|
|
18
20
|
demandOption: true
|
|
19
21
|
})
|
|
20
22
|
.help()
|
|
21
23
|
.parseSync();
|
|
22
24
|
return {
|
|
23
|
-
|
|
25
|
+
mcpToken: argv['access-token']
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
// ============================================================================
|
|
27
|
-
//
|
|
29
|
+
// Supabase Edge Functions Client
|
|
28
30
|
// ============================================================================
|
|
29
|
-
class
|
|
30
|
-
|
|
31
|
-
constructor(
|
|
32
|
-
this.
|
|
31
|
+
class SupabaseClient {
|
|
32
|
+
mcpToken;
|
|
33
|
+
constructor(mcpToken) {
|
|
34
|
+
this.mcpToken = mcpToken;
|
|
33
35
|
}
|
|
34
36
|
get headers() {
|
|
35
37
|
return {
|
|
36
|
-
'Authorization': `Bearer ${
|
|
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
|
|
41
|
-
const response = await axios.post(`${
|
|
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
|
|
45
|
-
|
|
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(`${
|
|
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(
|
|
53
|
-
const response = await axios.
|
|
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
|
|
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.
|
|
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.
|
|
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: '
|
|
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
|
|
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
|
|
160
|
+
responseText += `\n${i + 1}. ${source.title} (${(source.similarity * 100).toFixed(0)}%)`;
|
|
153
161
|
});
|
|
154
162
|
}
|
|
155
|
-
responseText += `\n\n_${result.metadata.documents_found}
|
|
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
|
|
169
|
+
const { content, title } = toolArgs;
|
|
162
170
|
if (!content || typeof content !== 'string') {
|
|
163
171
|
throw new Error('Content is required');
|
|
164
172
|
}
|
|
165
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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: `
|
|
241
|
+
content: [{ type: 'text', text: `Error (${status}): ${errorMsg}` }],
|
|
223
242
|
isError: true
|
|
224
243
|
};
|
|
225
244
|
}
|