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 +79 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +268 -0
- package/package.json +42 -0
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
|
package/dist/index.d.ts
ADDED
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
|
+
}
|