@chaaskit/server 0.1.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/dist/api/admin.js +438 -0
- package/dist/api/admin.js.map +1 -0
- package/dist/api/agents.js +21 -0
- package/dist/api/agents.js.map +1 -0
- package/dist/api/api-keys.js +122 -0
- package/dist/api/api-keys.js.map +1 -0
- package/dist/api/auth.js +399 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/chat.js +900 -0
- package/dist/api/chat.js.map +1 -0
- package/dist/api/config.js +91 -0
- package/dist/api/config.js.map +1 -0
- package/dist/api/documents.js +237 -0
- package/dist/api/documents.js.map +1 -0
- package/dist/api/export.js +107 -0
- package/dist/api/export.js.map +1 -0
- package/dist/api/health.js +25 -0
- package/dist/api/health.js.map +1 -0
- package/dist/api/mcp-server.js +84 -0
- package/dist/api/mcp-server.js.map +1 -0
- package/dist/api/mcp.js +400 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/mentions.js +94 -0
- package/dist/api/mentions.js.map +1 -0
- package/dist/api/oauth.js +366 -0
- package/dist/api/oauth.js.map +1 -0
- package/dist/api/payments.js +473 -0
- package/dist/api/payments.js.map +1 -0
- package/dist/api/projects.js +301 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/scheduled-prompts.js +617 -0
- package/dist/api/scheduled-prompts.js.map +1 -0
- package/dist/api/search.js +85 -0
- package/dist/api/search.js.map +1 -0
- package/dist/api/share.js +188 -0
- package/dist/api/share.js.map +1 -0
- package/dist/api/slack.js +468 -0
- package/dist/api/slack.js.map +1 -0
- package/dist/api/teams.js +693 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/api/templates.js +134 -0
- package/dist/api/templates.js.map +1 -0
- package/dist/api/threads.js +323 -0
- package/dist/api/threads.js.map +1 -0
- package/dist/api/upload.js +57 -0
- package/dist/api/upload.js.map +1 -0
- package/dist/api/user.js +111 -0
- package/dist/api/user.js.map +1 -0
- package/dist/api/v1/openai.js +245 -0
- package/dist/api/v1/openai.js.map +1 -0
- package/dist/app.js +168 -0
- package/dist/app.js.map +1 -0
- package/dist/bin/cli.js +57 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/commands/db-sync.js +108 -0
- package/dist/commands/db-sync.js.map +1 -0
- package/dist/config/loader.js +374 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/documents/extractors.js +136 -0
- package/dist/documents/extractors.js.map +1 -0
- package/dist/extensions/glob.js +53 -0
- package/dist/extensions/glob.js.map +1 -0
- package/dist/extensions/loader.js +72 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/index.js +75 -0
- package/dist/loaders/index.js.map +1 -0
- package/dist/mcp/client.js +551 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/server.js +335 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/middleware/apiKeyAuth.js +136 -0
- package/dist/middleware/apiKeyAuth.js.map +1 -0
- package/dist/middleware/auth.js +192 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/errorHandler.js +41 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/mcpServerAuth.js +164 -0
- package/dist/middleware/mcpServerAuth.js.map +1 -0
- package/dist/middleware/requestLogger.js +9 -0
- package/dist/middleware/requestLogger.js.map +1 -0
- package/dist/middleware/team.js +132 -0
- package/dist/middleware/team.js.map +1 -0
- package/dist/oauth/server.js +410 -0
- package/dist/oauth/server.js.map +1 -0
- package/dist/queue/cli.js +93 -0
- package/dist/queue/cli.js.map +1 -0
- package/dist/queue/handlers/index.js +91 -0
- package/dist/queue/handlers/index.js.map +1 -0
- package/dist/queue/handlers/scheduled-prompt.js +270 -0
- package/dist/queue/handlers/scheduled-prompt.js.map +1 -0
- package/dist/queue/index.js +91 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/providers/memory.js +296 -0
- package/dist/queue/providers/memory.js.map +1 -0
- package/dist/queue/providers/sqs.js +275 -0
- package/dist/queue/providers/sqs.js.map +1 -0
- package/dist/queue/scheduler.js +355 -0
- package/dist/queue/scheduler.js.map +1 -0
- package/dist/queue/types.js +5 -0
- package/dist/queue/types.js.map +1 -0
- package/dist/queue/worker.js +230 -0
- package/dist/queue/worker.js.map +1 -0
- package/dist/registry/index.js +40 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/server.js +207 -0
- package/dist/server.js.map +1 -0
- package/dist/services/agent.js +530 -0
- package/dist/services/agent.js.map +1 -0
- package/dist/services/agents.js +194 -0
- package/dist/services/agents.js.map +1 -0
- package/dist/services/documents.js +507 -0
- package/dist/services/documents.js.map +1 -0
- package/dist/services/email/index.js +91 -0
- package/dist/services/email/index.js.map +1 -0
- package/dist/services/email/providers/ses.js +97 -0
- package/dist/services/email/providers/ses.js.map +1 -0
- package/dist/services/email/templates.js +194 -0
- package/dist/services/email/templates.js.map +1 -0
- package/dist/services/email/types.js +5 -0
- package/dist/services/email/types.js.map +1 -0
- package/dist/services/encryption.js +69 -0
- package/dist/services/encryption.js.map +1 -0
- package/dist/services/oauth-discovery.js +226 -0
- package/dist/services/oauth-discovery.js.map +1 -0
- package/dist/services/pendingConfirmation.js +105 -0
- package/dist/services/pendingConfirmation.js.map +1 -0
- package/dist/services/scheduledPrompts.js +70 -0
- package/dist/services/scheduledPrompts.js.map +1 -0
- package/dist/services/slack/client.js +174 -0
- package/dist/services/slack/client.js.map +1 -0
- package/dist/services/slack/events.js +189 -0
- package/dist/services/slack/events.js.map +1 -0
- package/dist/services/slack/index.js +6 -0
- package/dist/services/slack/index.js.map +1 -0
- package/dist/services/slack/notifications.js +124 -0
- package/dist/services/slack/notifications.js.map +1 -0
- package/dist/services/slack/signature.js +74 -0
- package/dist/services/slack/signature.js.map +1 -0
- package/dist/services/slack/thread-context.js +191 -0
- package/dist/services/slack/thread-context.js.map +1 -0
- package/dist/services/toolConfirmation.js +55 -0
- package/dist/services/toolConfirmation.js.map +1 -0
- package/dist/services/usage.js +241 -0
- package/dist/services/usage.js.map +1 -0
- package/dist/ssr/build.js +90 -0
- package/dist/ssr/build.js.map +1 -0
- package/dist/ssr/components/SSRMessageList.js +120 -0
- package/dist/ssr/components/SSRMessageList.js.map +1 -0
- package/dist/ssr/entry.client.js +8 -0
- package/dist/ssr/entry.client.js.map +1 -0
- package/dist/ssr/entry.server.js +71 -0
- package/dist/ssr/entry.server.js.map +1 -0
- package/dist/ssr/handler.js +51 -0
- package/dist/ssr/handler.js.map +1 -0
- package/dist/ssr/root.js +184 -0
- package/dist/ssr/root.js.map +1 -0
- package/dist/ssr/routes/login.js +140 -0
- package/dist/ssr/routes/login.js.map +1 -0
- package/dist/ssr/routes/pricing.js +195 -0
- package/dist/ssr/routes/pricing.js.map +1 -0
- package/dist/ssr/routes/privacy.js +39 -0
- package/dist/ssr/routes/privacy.js.map +1 -0
- package/dist/ssr/routes/register.js +148 -0
- package/dist/ssr/routes/register.js.map +1 -0
- package/dist/ssr/routes/shared.$shareId.js +153 -0
- package/dist/ssr/routes/shared.$shareId.js.map +1 -0
- package/dist/ssr/routes/terms.js +39 -0
- package/dist/ssr/routes/terms.js.map +1 -0
- package/dist/storage/index.js +43 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/providers/database.js +38 -0
- package/dist/storage/providers/database.js.map +1 -0
- package/dist/storage/providers/filesystem.js +51 -0
- package/dist/storage/providers/filesystem.js.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/tools/documents.js +336 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/get-plan-usage.js +82 -0
- package/dist/tools/get-plan-usage.js.map +1 -0
- package/dist/tools/index.js +106 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/web-scrape.js +145 -0
- package/dist/tools/web-scrape.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Handler
|
|
3
|
+
*
|
|
4
|
+
* Implements JSON-RPC 2.0 MCP protocol for exposing this app's tools to external clients.
|
|
5
|
+
* Supports external MCP clients like Claude Desktop, MCP Inspector, etc.
|
|
6
|
+
*/
|
|
7
|
+
import { getAllNativeTools, executeNativeTool } from '../tools/index.js';
|
|
8
|
+
import { getConfig } from '../config/loader.js';
|
|
9
|
+
import { registry } from '../registry/index.js';
|
|
10
|
+
// MCP Protocol error codes (from spec)
|
|
11
|
+
const MCP_ERRORS = {
|
|
12
|
+
PARSE_ERROR: -32700,
|
|
13
|
+
INVALID_REQUEST: -32600,
|
|
14
|
+
METHOD_NOT_FOUND: -32601,
|
|
15
|
+
INVALID_PARAMS: -32602,
|
|
16
|
+
INTERNAL_ERROR: -32603,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Convert native tools to MCP tool format
|
|
20
|
+
*/
|
|
21
|
+
export function nativeToolsToMCPTools() {
|
|
22
|
+
const nativeTools = getAllNativeTools();
|
|
23
|
+
return nativeTools.map((tool) => ({
|
|
24
|
+
name: tool.name,
|
|
25
|
+
description: tool.description,
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: tool.inputSchema.properties,
|
|
29
|
+
required: tool.inputSchema.required,
|
|
30
|
+
},
|
|
31
|
+
_meta: tool._meta,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all tools to expose via MCP based on config
|
|
36
|
+
*/
|
|
37
|
+
function getExposedTools() {
|
|
38
|
+
const config = getConfig();
|
|
39
|
+
const serverConfig = config.mcp?.server;
|
|
40
|
+
if (!serverConfig?.enabled) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const exposeTools = serverConfig.exposeTools ?? 'native';
|
|
44
|
+
if (exposeTools === 'native') {
|
|
45
|
+
return nativeToolsToMCPTools();
|
|
46
|
+
}
|
|
47
|
+
if (exposeTools === 'all') {
|
|
48
|
+
// Get native tools + any extension-registered tools
|
|
49
|
+
return nativeToolsToMCPTools();
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(exposeTools)) {
|
|
52
|
+
// Filter to specific tools by name
|
|
53
|
+
const allTools = nativeToolsToMCPTools();
|
|
54
|
+
return allTools.filter((tool) => exposeTools.includes(tool.name));
|
|
55
|
+
}
|
|
56
|
+
return nativeToolsToMCPTools();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get MCP resources from registry (extension-provided)
|
|
60
|
+
*/
|
|
61
|
+
async function getMCPResources() {
|
|
62
|
+
const resourcesMap = registry.getAll('mcp-resource');
|
|
63
|
+
const resources = [];
|
|
64
|
+
for (const [, resource] of resourcesMap) {
|
|
65
|
+
resources.push({
|
|
66
|
+
uri: resource.uri,
|
|
67
|
+
name: resource.name,
|
|
68
|
+
description: resource.description,
|
|
69
|
+
mimeType: resource.mimeType,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return resources;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Read an MCP resource by URI
|
|
76
|
+
*/
|
|
77
|
+
async function readMCPResource(uri, context) {
|
|
78
|
+
const resourcesMap = registry.getAll('mcp-resource');
|
|
79
|
+
let foundResource = undefined;
|
|
80
|
+
for (const [, resource] of resourcesMap) {
|
|
81
|
+
if (resource.uri === uri) {
|
|
82
|
+
foundResource = resource;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!foundResource) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return await foundResource.read({ userId: context.userId });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(`[MCP Server] Error reading resource ${uri}:`, error);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Handle MCP protocol methods
|
|
99
|
+
*/
|
|
100
|
+
export async function handleMCPRequest(request, context) {
|
|
101
|
+
const { id, method, params } = request;
|
|
102
|
+
try {
|
|
103
|
+
switch (method) {
|
|
104
|
+
case 'initialize':
|
|
105
|
+
return handleInitialize(id, params);
|
|
106
|
+
case 'ping':
|
|
107
|
+
return handlePing(id);
|
|
108
|
+
case 'tools/list':
|
|
109
|
+
return handleToolsList(id);
|
|
110
|
+
case 'tools/call':
|
|
111
|
+
return await handleToolsCall(id, params, context);
|
|
112
|
+
case 'resources/list':
|
|
113
|
+
return await handleResourcesList(id);
|
|
114
|
+
case 'resources/read':
|
|
115
|
+
return await handleResourcesRead(id, params, context);
|
|
116
|
+
case 'notifications/initialized':
|
|
117
|
+
// Client notification - no response needed
|
|
118
|
+
return { jsonrpc: '2.0', id: null, result: {} };
|
|
119
|
+
default:
|
|
120
|
+
return {
|
|
121
|
+
jsonrpc: '2.0',
|
|
122
|
+
id: id ?? null,
|
|
123
|
+
error: {
|
|
124
|
+
code: MCP_ERRORS.METHOD_NOT_FOUND,
|
|
125
|
+
message: `Method not found: ${method}`,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error(`[MCP Server] Error handling ${method}:`, error);
|
|
132
|
+
return {
|
|
133
|
+
jsonrpc: '2.0',
|
|
134
|
+
id: id ?? null,
|
|
135
|
+
error: {
|
|
136
|
+
code: MCP_ERRORS.INTERNAL_ERROR,
|
|
137
|
+
message: error instanceof Error ? error.message : 'Internal error',
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Handle initialize method
|
|
144
|
+
*/
|
|
145
|
+
function handleInitialize(id, params) {
|
|
146
|
+
const config = getConfig();
|
|
147
|
+
const appName = config.app?.name || 'Chat SaaS';
|
|
148
|
+
return {
|
|
149
|
+
jsonrpc: '2.0',
|
|
150
|
+
id: id ?? null,
|
|
151
|
+
result: {
|
|
152
|
+
protocolVersion: '2024-11-05',
|
|
153
|
+
serverInfo: {
|
|
154
|
+
name: appName,
|
|
155
|
+
version: '1.0.0',
|
|
156
|
+
},
|
|
157
|
+
capabilities: {
|
|
158
|
+
tools: {},
|
|
159
|
+
resources: {},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Handle ping method
|
|
166
|
+
*/
|
|
167
|
+
function handlePing(id) {
|
|
168
|
+
return {
|
|
169
|
+
jsonrpc: '2.0',
|
|
170
|
+
id: id ?? null,
|
|
171
|
+
result: {},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Handle tools/list method
|
|
176
|
+
*/
|
|
177
|
+
function handleToolsList(id) {
|
|
178
|
+
const tools = getExposedTools();
|
|
179
|
+
return {
|
|
180
|
+
jsonrpc: '2.0',
|
|
181
|
+
id: id ?? null,
|
|
182
|
+
result: {
|
|
183
|
+
tools: tools.map((tool) => ({
|
|
184
|
+
name: tool.name,
|
|
185
|
+
description: tool.description,
|
|
186
|
+
inputSchema: tool.inputSchema,
|
|
187
|
+
_meta: tool._meta,
|
|
188
|
+
})),
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Handle tools/call method
|
|
194
|
+
*/
|
|
195
|
+
async function handleToolsCall(id, params, context) {
|
|
196
|
+
if (!params?.name || typeof params.name !== 'string') {
|
|
197
|
+
return {
|
|
198
|
+
jsonrpc: '2.0',
|
|
199
|
+
id: id ?? null,
|
|
200
|
+
error: {
|
|
201
|
+
code: MCP_ERRORS.INVALID_PARAMS,
|
|
202
|
+
message: 'Missing required parameter: name',
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const toolName = params.name;
|
|
207
|
+
const args = params.arguments || {};
|
|
208
|
+
// Check if tool is exposed
|
|
209
|
+
const exposedTools = getExposedTools();
|
|
210
|
+
const tool = exposedTools.find((t) => t.name === toolName);
|
|
211
|
+
if (!tool) {
|
|
212
|
+
return {
|
|
213
|
+
jsonrpc: '2.0',
|
|
214
|
+
id: id ?? null,
|
|
215
|
+
error: {
|
|
216
|
+
code: MCP_ERRORS.INVALID_PARAMS,
|
|
217
|
+
message: `Tool not found: ${toolName}`,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
console.log(`[MCP Server] Executing tool: ${toolName}`);
|
|
222
|
+
// Execute the native tool
|
|
223
|
+
const result = await executeNativeTool(toolName, args, {
|
|
224
|
+
userId: context.userId,
|
|
225
|
+
threadId: undefined,
|
|
226
|
+
agentId: undefined,
|
|
227
|
+
});
|
|
228
|
+
return {
|
|
229
|
+
jsonrpc: '2.0',
|
|
230
|
+
id: id ?? null,
|
|
231
|
+
result: {
|
|
232
|
+
content: result.content,
|
|
233
|
+
isError: result.isError,
|
|
234
|
+
structuredContent: result.structuredContent,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Handle resources/list method
|
|
240
|
+
*/
|
|
241
|
+
async function handleResourcesList(id) {
|
|
242
|
+
const resources = await getMCPResources();
|
|
243
|
+
return {
|
|
244
|
+
jsonrpc: '2.0',
|
|
245
|
+
id: id ?? null,
|
|
246
|
+
result: {
|
|
247
|
+
resources,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Handle resources/read method
|
|
253
|
+
*/
|
|
254
|
+
async function handleResourcesRead(id, params, context) {
|
|
255
|
+
if (!params?.uri || typeof params.uri !== 'string') {
|
|
256
|
+
return {
|
|
257
|
+
jsonrpc: '2.0',
|
|
258
|
+
id: id ?? null,
|
|
259
|
+
error: {
|
|
260
|
+
code: MCP_ERRORS.INVALID_PARAMS,
|
|
261
|
+
message: 'Missing required parameter: uri',
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const uri = params.uri;
|
|
266
|
+
const result = await readMCPResource(uri, context);
|
|
267
|
+
if (!result) {
|
|
268
|
+
return {
|
|
269
|
+
jsonrpc: '2.0',
|
|
270
|
+
id: id ?? null,
|
|
271
|
+
error: {
|
|
272
|
+
code: MCP_ERRORS.INVALID_PARAMS,
|
|
273
|
+
message: `Resource not found: ${uri}`,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
// Get resource metadata
|
|
278
|
+
const resources = await getMCPResources();
|
|
279
|
+
const resourceMeta = resources.find((r) => r.uri === uri);
|
|
280
|
+
return {
|
|
281
|
+
jsonrpc: '2.0',
|
|
282
|
+
id: id ?? null,
|
|
283
|
+
result: {
|
|
284
|
+
contents: [
|
|
285
|
+
{
|
|
286
|
+
uri,
|
|
287
|
+
mimeType: resourceMeta?.mimeType || 'text/plain',
|
|
288
|
+
text: result.text,
|
|
289
|
+
blob: result.blob,
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Parse and validate a JSON-RPC request
|
|
297
|
+
*/
|
|
298
|
+
export function parseJsonRpcRequest(body) {
|
|
299
|
+
if (!body || typeof body !== 'object') {
|
|
300
|
+
return {
|
|
301
|
+
code: MCP_ERRORS.PARSE_ERROR,
|
|
302
|
+
message: 'Invalid JSON',
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const req = body;
|
|
306
|
+
if (req.jsonrpc !== '2.0') {
|
|
307
|
+
return {
|
|
308
|
+
code: MCP_ERRORS.INVALID_REQUEST,
|
|
309
|
+
message: 'Invalid JSON-RPC version',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (typeof req.method !== 'string') {
|
|
313
|
+
return {
|
|
314
|
+
code: MCP_ERRORS.INVALID_REQUEST,
|
|
315
|
+
message: 'Missing or invalid method',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
jsonrpc: '2.0',
|
|
320
|
+
id: req.id,
|
|
321
|
+
method: req.method,
|
|
322
|
+
params: req.params,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Create an error response for parse/validation errors
|
|
327
|
+
*/
|
|
328
|
+
export function createErrorResponse(error) {
|
|
329
|
+
return {
|
|
330
|
+
jsonrpc: '2.0',
|
|
331
|
+
id: null,
|
|
332
|
+
error,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import bcrypt from 'bcryptjs';
|
|
2
|
+
import { db } from '@chaaskit/db';
|
|
3
|
+
import { HTTP_STATUS } from '@chaaskit/shared';
|
|
4
|
+
import { getConfig } from '../config/loader.js';
|
|
5
|
+
/**
|
|
6
|
+
* Check if a path matches a pattern.
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Exact match: "/api/threads"
|
|
9
|
+
* - Single segment wildcard: "/api/threads/*" matches "/api/threads/123" but not "/api/threads/123/messages"
|
|
10
|
+
* - Multi-segment wildcard: "/api/threads/**" matches "/api/threads/123" and "/api/threads/123/messages"
|
|
11
|
+
*/
|
|
12
|
+
function matchesPattern(path, pattern) {
|
|
13
|
+
// Exact match
|
|
14
|
+
if (pattern === path) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
// Handle ** (matches any depth)
|
|
18
|
+
if (pattern.endsWith('/**')) {
|
|
19
|
+
const prefix = pattern.slice(0, -3);
|
|
20
|
+
return path === prefix || path.startsWith(prefix + '/');
|
|
21
|
+
}
|
|
22
|
+
// Handle * (matches single segment)
|
|
23
|
+
if (pattern.endsWith('/*')) {
|
|
24
|
+
const prefix = pattern.slice(0, -2);
|
|
25
|
+
if (!path.startsWith(prefix + '/')) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const remainder = path.slice(prefix.length + 1);
|
|
29
|
+
// Should not contain another slash (single segment only)
|
|
30
|
+
return !remainder.includes('/');
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if the request path is allowed for API key access.
|
|
36
|
+
*/
|
|
37
|
+
function isEndpointAllowed(path, allowedEndpoints) {
|
|
38
|
+
for (const pattern of allowedEndpoints) {
|
|
39
|
+
if (matchesPattern(path, pattern)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Middleware to authenticate requests using API keys.
|
|
47
|
+
* Should be used before requireAuth in the middleware chain.
|
|
48
|
+
*
|
|
49
|
+
* API key access is restricted to endpoints listed in config.api.allowedEndpoints.
|
|
50
|
+
* If no endpoints are configured, API keys cannot be used for any endpoint.
|
|
51
|
+
*/
|
|
52
|
+
export async function apiKeyAuth(req, res, next) {
|
|
53
|
+
try {
|
|
54
|
+
const authHeader = req.headers.authorization;
|
|
55
|
+
const config = getConfig();
|
|
56
|
+
const keyPrefix = config.api?.keyPrefix || 'sk-';
|
|
57
|
+
// Check if this looks like an API key
|
|
58
|
+
const isApiKeyAuth = authHeader?.startsWith(`Bearer ${keyPrefix}`);
|
|
59
|
+
if (!isApiKeyAuth) {
|
|
60
|
+
return next(); // Not an API key, let other auth handle it
|
|
61
|
+
}
|
|
62
|
+
// It's an API key - check if this endpoint is allowed
|
|
63
|
+
const allowedEndpoints = config.api?.allowedEndpoints || [];
|
|
64
|
+
if (allowedEndpoints.length === 0) {
|
|
65
|
+
res.status(HTTP_STATUS.FORBIDDEN).json({
|
|
66
|
+
error: 'API key access is not enabled for any endpoints'
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!isEndpointAllowed(req.path, allowedEndpoints)) {
|
|
71
|
+
res.status(HTTP_STATUS.FORBIDDEN).json({
|
|
72
|
+
error: 'API key access is not allowed for this endpoint'
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const apiKey = authHeader.slice(7); // Remove "Bearer "
|
|
77
|
+
// Find keys by prefix and check hash
|
|
78
|
+
// The stored keyPrefix is: configuredPrefix + 6 random chars
|
|
79
|
+
const storedPrefixLength = keyPrefix.length + 6;
|
|
80
|
+
const searchPrefix = apiKey.slice(0, storedPrefixLength);
|
|
81
|
+
const candidates = await db.apiKey.findMany({
|
|
82
|
+
where: { keyPrefix: searchPrefix },
|
|
83
|
+
include: {
|
|
84
|
+
user: {
|
|
85
|
+
select: {
|
|
86
|
+
id: true,
|
|
87
|
+
email: true,
|
|
88
|
+
name: true,
|
|
89
|
+
avatarUrl: true,
|
|
90
|
+
isAdmin: true,
|
|
91
|
+
emailVerified: true,
|
|
92
|
+
plan: true,
|
|
93
|
+
credits: true,
|
|
94
|
+
messagesThisMonth: true,
|
|
95
|
+
themePreference: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
team: true,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
for (const candidate of candidates) {
|
|
102
|
+
if (await bcrypt.compare(apiKey, candidate.keyHash)) {
|
|
103
|
+
// Check expiration
|
|
104
|
+
if (candidate.expiresAt && candidate.expiresAt < new Date()) {
|
|
105
|
+
res.status(HTTP_STATUS.UNAUTHORIZED).json({ error: 'API key expired' });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// If team-scoped, verify user is still a member
|
|
109
|
+
if (candidate.teamId) {
|
|
110
|
+
const membership = await db.teamMember.findFirst({
|
|
111
|
+
where: { userId: candidate.userId, teamId: candidate.teamId },
|
|
112
|
+
});
|
|
113
|
+
if (!membership) {
|
|
114
|
+
res.status(HTTP_STATUS.UNAUTHORIZED).json({ error: 'API key invalid - no longer a team member' });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Update lastUsedAt (fire and forget)
|
|
119
|
+
db.apiKey.update({
|
|
120
|
+
where: { id: candidate.id },
|
|
121
|
+
data: { lastUsedAt: new Date() },
|
|
122
|
+
}).catch(() => { });
|
|
123
|
+
req.user = candidate.user;
|
|
124
|
+
// Set team context for team-scoped keys
|
|
125
|
+
req.apiKeyTeamId = candidate.teamId || undefined;
|
|
126
|
+
return next();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// No valid key found
|
|
130
|
+
res.status(HTTP_STATUS.UNAUTHORIZED).json({ error: 'Invalid API key' });
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
next(error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=apiKeyAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKeyAuth.js","sourceRoot":"","sources":["../../src/middleware/apiKeyAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
import { db } from '@chaaskit/db';
|
|
3
|
+
import { AppError } from './errorHandler.js';
|
|
4
|
+
import { HTTP_STATUS } from '@chaaskit/shared';
|
|
5
|
+
import { getConfig } from '../config/loader.js';
|
|
6
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
|
7
|
+
export function generateToken(userId, email) {
|
|
8
|
+
return jwt.sign({ userId, email }, JWT_SECRET, { expiresIn: '7d' });
|
|
9
|
+
}
|
|
10
|
+
export function verifyToken(token) {
|
|
11
|
+
return jwt.verify(token, JWT_SECRET);
|
|
12
|
+
}
|
|
13
|
+
export async function requireAuth(req, res, next) {
|
|
14
|
+
try {
|
|
15
|
+
// If user is already authenticated (e.g., by apiKeyAuth middleware), skip JWT validation
|
|
16
|
+
if (req.user) {
|
|
17
|
+
next();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const config = getConfig();
|
|
21
|
+
// Check for token in Authorization header or cookie
|
|
22
|
+
const authHeader = req.headers.authorization;
|
|
23
|
+
const cookieToken = req.cookies?.token;
|
|
24
|
+
const token = authHeader?.startsWith('Bearer ')
|
|
25
|
+
? authHeader.slice(7)
|
|
26
|
+
: cookieToken;
|
|
27
|
+
if (!token) {
|
|
28
|
+
// Allow unauthenticated access if configured
|
|
29
|
+
if (config.auth.allowUnauthenticated) {
|
|
30
|
+
next();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
throw new AppError(HTTP_STATUS.UNAUTHORIZED, 'Authentication required');
|
|
34
|
+
}
|
|
35
|
+
const payload = verifyToken(token);
|
|
36
|
+
const user = await db.user.findUnique({
|
|
37
|
+
where: { id: payload.userId },
|
|
38
|
+
select: {
|
|
39
|
+
id: true,
|
|
40
|
+
email: true,
|
|
41
|
+
name: true,
|
|
42
|
+
avatarUrl: true,
|
|
43
|
+
isAdmin: true,
|
|
44
|
+
emailVerified: true,
|
|
45
|
+
plan: true,
|
|
46
|
+
credits: true,
|
|
47
|
+
messagesThisMonth: true,
|
|
48
|
+
themePreference: true,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
if (!user) {
|
|
52
|
+
throw new AppError(HTTP_STATUS.UNAUTHORIZED, 'User not found');
|
|
53
|
+
}
|
|
54
|
+
req.user = user;
|
|
55
|
+
next();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof AppError) {
|
|
59
|
+
next(error);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
next(new AppError(HTTP_STATUS.UNAUTHORIZED, 'Invalid token'));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function optionalAuth(req, res, next) {
|
|
66
|
+
try {
|
|
67
|
+
// If user is already authenticated (e.g., by apiKeyAuth middleware), skip JWT validation
|
|
68
|
+
if (req.user) {
|
|
69
|
+
next();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const authHeader = req.headers.authorization;
|
|
73
|
+
const cookieToken = req.cookies?.token;
|
|
74
|
+
const token = authHeader?.startsWith('Bearer ')
|
|
75
|
+
? authHeader.slice(7)
|
|
76
|
+
: cookieToken;
|
|
77
|
+
if (!token) {
|
|
78
|
+
next();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const payload = verifyToken(token);
|
|
82
|
+
const user = await db.user.findUnique({
|
|
83
|
+
where: { id: payload.userId },
|
|
84
|
+
select: {
|
|
85
|
+
id: true,
|
|
86
|
+
email: true,
|
|
87
|
+
name: true,
|
|
88
|
+
avatarUrl: true,
|
|
89
|
+
isAdmin: true,
|
|
90
|
+
emailVerified: true,
|
|
91
|
+
plan: true,
|
|
92
|
+
credits: true,
|
|
93
|
+
messagesThisMonth: true,
|
|
94
|
+
themePreference: true,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
if (user) {
|
|
98
|
+
req.user = user;
|
|
99
|
+
}
|
|
100
|
+
next();
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Invalid token, continue without auth
|
|
104
|
+
next();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export async function requireAdmin(req, res, next) {
|
|
108
|
+
if (!req.user) {
|
|
109
|
+
next(new AppError(HTTP_STATUS.UNAUTHORIZED, 'Authentication required'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const config = getConfig();
|
|
113
|
+
const adminEmails = config.admin?.emails || [];
|
|
114
|
+
// Check if user's email is in the admin list
|
|
115
|
+
const isConfigAdmin = adminEmails.some((email) => email.toLowerCase() === req.user.email.toLowerCase());
|
|
116
|
+
// Also check the database isAdmin flag for backward compatibility
|
|
117
|
+
if (!isConfigAdmin && !req.user.isAdmin) {
|
|
118
|
+
next(new AppError(HTTP_STATUS.FORBIDDEN, 'Admin access required'));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
next();
|
|
122
|
+
}
|
|
123
|
+
import { isEmailEnabled } from '../services/email/index.js';
|
|
124
|
+
/**
|
|
125
|
+
* Check if email verification is required for the current user.
|
|
126
|
+
* Returns true if user needs verification, false otherwise.
|
|
127
|
+
*/
|
|
128
|
+
function shouldBlockUnverifiedUser(req) {
|
|
129
|
+
const config = getConfig();
|
|
130
|
+
// No user = nothing to verify
|
|
131
|
+
if (!req.user) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
// Feature not enabled
|
|
135
|
+
if (!config.auth.emailVerification?.enabled) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
// Email provider not configured (graceful degradation)
|
|
139
|
+
if (!isEmailEnabled()) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
// User is already verified
|
|
143
|
+
if (req.user.emailVerified) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
// User needs verification
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Middleware that requires the user to have a verified email.
|
|
151
|
+
* Skips the check if email verification is not enabled or email provider is not configured.
|
|
152
|
+
* Use after requireAuth middleware.
|
|
153
|
+
*/
|
|
154
|
+
export async function requireVerifiedEmail(req, res, next) {
|
|
155
|
+
if (!req.user) {
|
|
156
|
+
next(new AppError(HTTP_STATUS.UNAUTHORIZED, 'Authentication required'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (shouldBlockUnverifiedUser(req)) {
|
|
160
|
+
res.status(HTTP_STATUS.FORBIDDEN).json({
|
|
161
|
+
error: 'Email not verified',
|
|
162
|
+
code: 'EMAIL_NOT_VERIFIED',
|
|
163
|
+
message: 'Please verify your email address to continue',
|
|
164
|
+
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
next();
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Middleware that checks email verification for optional auth routes.
|
|
171
|
+
* If user is authenticated but not verified, blocks the request.
|
|
172
|
+
* If user is not authenticated, allows the request (for allowUnauthenticated mode).
|
|
173
|
+
* Use after optionalAuth middleware.
|
|
174
|
+
*/
|
|
175
|
+
export async function optionalVerifiedEmail(req, res, next) {
|
|
176
|
+
// If no user, allow (unauthenticated access may be allowed)
|
|
177
|
+
if (!req.user) {
|
|
178
|
+
next();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// If user exists but needs verification, block
|
|
182
|
+
if (shouldBlockUnverifiedUser(req)) {
|
|
183
|
+
res.status(HTTP_STATUS.FORBIDDEN).json({
|
|
184
|
+
error: 'Email not verified',
|
|
185
|
+
code: 'EMAIL_NOT_VERIFIED',
|
|
186
|
+
message: 'Please verify your email address to continue',
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
next();
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=auth.js.map
|