session-collab-mcp 0.3.0 → 0.4.3

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/src/index.ts DELETED
@@ -1,438 +0,0 @@
1
- // Session Collaboration MCP - Cloudflare Worker Entry Point
2
- // Implements MCP SSE transport for Claude Code integration
3
-
4
- import type { D1Database } from '@cloudflare/workers-types';
5
- import { McpServer, parseRequest } from './mcp/server';
6
- import { createErrorResponse, MCP_ERROR_CODES, type JsonRpcResponse } from './mcp/protocol';
7
- import { validateAuth, unauthorizedResponse, type Env as AuthEnv } from './auth/middleware';
8
- import type { AuthContext } from './auth/types';
9
- import {
10
- handleRegister,
11
- handleLogin,
12
- handleRefresh,
13
- handleLogout,
14
- handleGetMe,
15
- handleUpdateMe,
16
- handleChangePassword,
17
- } from './auth/handlers';
18
- import { handleCreateToken, handleListTokens, handleRevokeToken } from './tokens/handlers';
19
- import { generateAppHtml } from './frontend/app';
20
-
21
- export interface Env extends AuthEnv {
22
- DB: D1Database;
23
- API_TOKEN?: string;
24
- JWT_SECRET?: string;
25
- ENVIRONMENT: string;
26
- }
27
-
28
- // CORS headers for cross-origin requests
29
- const corsHeaders = {
30
- 'Access-Control-Allow-Origin': '*',
31
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
32
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
33
- };
34
-
35
- // Handle MCP SSE endpoint
36
- async function handleMcpRequest(request: Request, env: Env, authContext: AuthContext | null): Promise<Response> {
37
- // Validate auth - require authentication if JWT_SECRET or API_TOKEN is configured
38
- if ((env.JWT_SECRET || env.API_TOKEN) && !authContext) {
39
- return unauthorizedResponse();
40
- }
41
-
42
- const server = new McpServer(env.DB, authContext ?? undefined);
43
-
44
- // Handle SSE GET request (for establishing connection)
45
- if (request.method === 'GET') {
46
- return new Response(
47
- JSON.stringify({
48
- type: 'sse',
49
- endpoint: '/mcp',
50
- description: 'Session Collaboration MCP Server',
51
- instructions: 'POST JSON-RPC requests to this endpoint',
52
- }),
53
- {
54
- headers: { 'Content-Type': 'application/json', ...corsHeaders },
55
- }
56
- );
57
- }
58
-
59
- // Handle POST request (MCP message)
60
- if (request.method === 'POST') {
61
- const body = await request.text();
62
- const jsonRpcRequest = parseRequest(body);
63
-
64
- let response: JsonRpcResponse;
65
-
66
- if (!jsonRpcRequest) {
67
- response = createErrorResponse(undefined, MCP_ERROR_CODES.PARSE_ERROR, 'Invalid JSON-RPC request');
68
- } else {
69
- response = await server.handleRequest(jsonRpcRequest);
70
- }
71
-
72
- return new Response(JSON.stringify(response), {
73
- headers: { 'Content-Type': 'application/json', ...corsHeaders },
74
- });
75
- }
76
-
77
- return new Response('Method not allowed', { status: 405 });
78
- }
79
-
80
- // Handle auth routes
81
- async function handleAuthRoute(request: Request, env: Env, pathname: string, authContext: AuthContext | null): Promise<Response> {
82
- const ctx = { db: env.DB, jwtSecret: env.JWT_SECRET ?? '', request };
83
-
84
- // Public auth routes (no auth required)
85
- if (request.method === 'POST') {
86
- if (pathname === '/auth/register') {
87
- return handleRegister(ctx);
88
- }
89
- if (pathname === '/auth/login') {
90
- return handleLogin(ctx);
91
- }
92
- if (pathname === '/auth/refresh') {
93
- return handleRefresh(ctx);
94
- }
95
- }
96
-
97
- // Protected auth routes (auth required)
98
- if (!authContext) {
99
- return unauthorizedResponse();
100
- }
101
-
102
- if (request.method === 'POST' && pathname === '/auth/logout') {
103
- return handleLogout(ctx, authContext);
104
- }
105
-
106
- if (pathname === '/auth/me') {
107
- if (request.method === 'GET') {
108
- return handleGetMe(ctx, authContext);
109
- }
110
- if (request.method === 'PUT') {
111
- return handleUpdateMe(ctx, authContext);
112
- }
113
- }
114
-
115
- if (request.method === 'PUT' && pathname === '/auth/password') {
116
- return handleChangePassword(ctx, authContext);
117
- }
118
-
119
- return new Response('Not found', { status: 404 });
120
- }
121
-
122
- // Handle token routes
123
- async function handleTokenRoute(request: Request, env: Env, pathname: string, authContext: AuthContext | null): Promise<Response> {
124
- if (!authContext) {
125
- return unauthorizedResponse();
126
- }
127
-
128
- const ctx = { db: env.DB, request };
129
-
130
- // POST /tokens - create token
131
- if (request.method === 'POST' && pathname === '/tokens') {
132
- return handleCreateToken(ctx, authContext);
133
- }
134
-
135
- // GET /tokens - list tokens
136
- if (request.method === 'GET' && pathname === '/tokens') {
137
- return handleListTokens(ctx, authContext);
138
- }
139
-
140
- // DELETE /tokens/:id - revoke token
141
- const deleteMatch = pathname.match(/^\/tokens\/([a-f0-9-]+)$/);
142
- if (request.method === 'DELETE' && deleteMatch) {
143
- const tokenId = deleteMatch[1];
144
- return handleRevokeToken(ctx, authContext, tokenId);
145
- }
146
-
147
- return new Response('Not found', { status: 404 });
148
- }
149
-
150
- // Health check endpoint (JSON)
151
- function handleHealthCheck(env: Env): Response {
152
- return new Response(
153
- JSON.stringify({
154
- status: 'healthy',
155
- service: 'session-collab-mcp',
156
- version: '0.2.0',
157
- environment: env.ENVIRONMENT,
158
- auth_enabled: !!(env.JWT_SECRET || env.API_TOKEN),
159
- }),
160
- {
161
- headers: { 'Content-Type': 'application/json', ...corsHeaders },
162
- }
163
- );
164
- }
165
-
166
- // Homepage - MCP service info and quick start
167
- function handleHomepage(env: Env, request: Request): Response {
168
- const url = new URL(request.url);
169
- const origin = url.origin;
170
- const html = `<!DOCTYPE html>
171
- <html lang="en">
172
- <head>
173
- <meta charset="UTF-8">
174
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
175
- <title>Session Collab MCP</title>
176
- <style>
177
- * { margin: 0; padding: 0; box-sizing: border-box; }
178
- body {
179
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
180
- background: #0f172a;
181
- color: #e2e8f0;
182
- min-height: 100vh;
183
- padding: 2rem;
184
- }
185
- .container { max-width: 720px; margin: 0 auto; }
186
- header { text-align: center; margin-bottom: 2rem; }
187
- h1 { font-size: 1.75rem; font-weight: 600; margin-bottom: 0.5rem; }
188
- .status {
189
- display: inline-flex;
190
- align-items: center;
191
- gap: 0.5rem;
192
- background: rgba(34, 197, 94, 0.1);
193
- border: 1px solid rgba(34, 197, 94, 0.3);
194
- color: #22c55e;
195
- padding: 0.25rem 0.75rem;
196
- border-radius: 9999px;
197
- font-size: 0.75rem;
198
- }
199
- .dot {
200
- width: 6px; height: 6px;
201
- background: #22c55e;
202
- border-radius: 50%;
203
- }
204
- .version { color: #64748b; font-size: 0.75rem; margin-top: 0.5rem; }
205
- .card {
206
- background: rgba(255,255,255,0.03);
207
- border: 1px solid rgba(255,255,255,0.08);
208
- border-radius: 0.75rem;
209
- padding: 1.25rem;
210
- margin-bottom: 1rem;
211
- }
212
- h2 { font-size: 1rem; color: #94a3b8; margin-bottom: 1rem; font-weight: 500; }
213
- .tools {
214
- display: grid;
215
- grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
216
- gap: 0.5rem;
217
- }
218
- .tool {
219
- background: rgba(99, 102, 241, 0.1);
220
- border: 1px solid rgba(99, 102, 241, 0.2);
221
- border-radius: 0.5rem;
222
- padding: 0.5rem 0.75rem;
223
- font-size: 0.8rem;
224
- }
225
- .tool code { color: #818cf8; }
226
- .tool span { color: #64748b; font-size: 0.7rem; display: block; margin-top: 0.125rem; }
227
- .step { display: flex; gap: 0.75rem; margin-bottom: 1rem; }
228
- .step:last-child { margin-bottom: 0; }
229
- .num {
230
- flex-shrink: 0;
231
- width: 1.5rem; height: 1.5rem;
232
- background: #3b82f6;
233
- border-radius: 50%;
234
- display: flex;
235
- align-items: center;
236
- justify-content: center;
237
- font-size: 0.75rem;
238
- font-weight: 600;
239
- }
240
- .step-content { flex: 1; }
241
- .step-content h3 { font-size: 0.875rem; margin-bottom: 0.375rem; font-weight: 500; }
242
- pre {
243
- background: #020617;
244
- border: 1px solid rgba(255,255,255,0.08);
245
- border-radius: 0.375rem;
246
- padding: 0.75rem;
247
- font-size: 0.7rem;
248
- overflow-x: auto;
249
- line-height: 1.5;
250
- }
251
- code { color: #7dd3fc; }
252
- .copy-btn {
253
- background: rgba(59, 130, 246, 0.2);
254
- border: 1px solid rgba(59, 130, 246, 0.3);
255
- color: #60a5fa;
256
- padding: 0.2rem 0.5rem;
257
- border-radius: 0.25rem;
258
- cursor: pointer;
259
- font-size: 0.65rem;
260
- margin-top: 0.375rem;
261
- }
262
- .copy-btn:hover { background: rgba(59, 130, 246, 0.3); }
263
- footer { text-align: center; color: #475569; font-size: 0.7rem; margin-top: 1.5rem; }
264
- .login-btn {
265
- display: inline-block;
266
- margin-top: 1rem;
267
- padding: 0.5rem 1.5rem;
268
- background: linear-gradient(135deg, #3b82f6, #8b5cf6);
269
- color: #fff;
270
- text-decoration: none;
271
- border-radius: 0.5rem;
272
- font-size: 0.875rem;
273
- font-weight: 500;
274
- transition: opacity 0.2s;
275
- }
276
- .login-btn:hover { opacity: 0.9; }
277
- </style>
278
- </head>
279
- <body>
280
- <div class="container">
281
- <header>
282
- <h1>Session Collab MCP</h1>
283
- <div class="status"><span class="dot"></span>Operational</div>
284
- <p class="version">v0.2.0 · ${env.ENVIRONMENT}</p>
285
- <a href="/app" class="login-btn">Login</a>
286
- </header>
287
-
288
- <div class="card">
289
- <h2>Quick Start</h2>
290
-
291
- <div class="step">
292
- <div class="num">1</div>
293
- <div class="step-content">
294
- <h3>Register & Login</h3>
295
- <pre id="s1">curl -X POST ${origin}/auth/register -H "Content-Type: application/json" \\
296
- -d '{"email": "you@example.com", "password": "YourPass123"}'</pre>
297
- <button class="copy-btn" onclick="copy('s1')">Copy</button>
298
- </div>
299
- </div>
300
-
301
- <div class="step">
302
- <div class="num">2</div>
303
- <div class="step-content">
304
- <h3>Create API Token</h3>
305
- <pre id="s2">curl -X POST ${origin}/tokens -H "Content-Type: application/json" \\
306
- -H "Authorization: Bearer ACCESS_TOKEN" -d '{"name": "My Machine"}'</pre>
307
- <button class="copy-btn" onclick="copy('s2')">Copy</button>
308
- </div>
309
- </div>
310
-
311
- <div class="step">
312
- <div class="num">3</div>
313
- <div class="step-content">
314
- <h3>Configure Claude Code</h3>
315
- <pre id="s3">{
316
- "mcpServers": {
317
- "session-collab": {
318
- "type": "http",
319
- "url": "${origin}/mcp",
320
- "headers": {
321
- "Authorization": "Bearer mcp_YOUR_TOKEN"
322
- }
323
- }
324
- }
325
- }</pre>
326
- <button class="copy-btn" onclick="copy('s3')">Copy</button>
327
- </div>
328
- </div>
329
- </div>
330
-
331
- <div class="card">
332
- <h2>MCP Tools</h2>
333
- <div class="tools">
334
- <div class="tool"><code>collab_session_start</code><span>Start a session</span></div>
335
- <div class="tool"><code>collab_session_end</code><span>End a session</span></div>
336
- <div class="tool"><code>collab_session_list</code><span>List sessions</span></div>
337
- <div class="tool"><code>collab_session_heartbeat</code><span>Update heartbeat</span></div>
338
- <div class="tool"><code>collab_claim</code><span>Claim files</span></div>
339
- <div class="tool"><code>collab_check</code><span>Check conflicts</span></div>
340
- <div class="tool"><code>collab_release</code><span>Release claim</span></div>
341
- <div class="tool"><code>collab_claims_list</code><span>List all claims</span></div>
342
- <div class="tool"><code>collab_message_send</code><span>Send message</span></div>
343
- <div class="tool"><code>collab_message_list</code><span>Read messages</span></div>
344
- <div class="tool"><code>collab_decision_add</code><span>Record decision</span></div>
345
- <div class="tool"><code>collab_decision_list</code><span>List decisions</span></div>
346
- </div>
347
- </div>
348
-
349
- <footer>Powered by Cloudflare Workers + D1</footer>
350
- </div>
351
- <script>
352
- function copy(id) {
353
- navigator.clipboard.writeText(document.getElementById(id).textContent);
354
- event.target.textContent = 'Copied!';
355
- setTimeout(() => event.target.textContent = 'Copy', 1500);
356
- }
357
- </script>
358
- </body>
359
- </html>`;
360
-
361
- return new Response(html, {
362
- headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders },
363
- });
364
- }
365
-
366
- // Main request handler
367
- export default {
368
- async fetch(request: Request, env: Env): Promise<Response> {
369
- const url = new URL(request.url);
370
-
371
- // Handle CORS preflight
372
- if (request.method === 'OPTIONS') {
373
- return new Response(null, {
374
- status: 204,
375
- headers: corsHeaders,
376
- });
377
- }
378
-
379
- // Validate auth once for all routes
380
- const authContext = await validateAuth(request, env);
381
-
382
- // Route requests
383
- const pathname = url.pathname;
384
-
385
- // Homepage
386
- if (pathname === '/') {
387
- return handleHomepage(env, request);
388
- }
389
-
390
- // Dashboard App
391
- if (pathname === '/app' || pathname === '/dashboard') {
392
- const html = generateAppHtml(url.origin);
393
- return new Response(html, {
394
- headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders },
395
- });
396
- }
397
-
398
- // Health check
399
- if (pathname === '/health') {
400
- return handleHealthCheck(env);
401
- }
402
-
403
- // Auth routes
404
- if (pathname.startsWith('/auth/')) {
405
- return handleAuthRoute(request, env, pathname, authContext);
406
- }
407
-
408
- // Token routes
409
- if (pathname.startsWith('/tokens')) {
410
- return handleTokenRoute(request, env, pathname, authContext);
411
- }
412
-
413
- // MCP routes
414
- if (pathname === '/mcp' || pathname === '/mcp/' || pathname === '/sse' || pathname === '/sse/') {
415
- return handleMcpRequest(request, env, authContext);
416
- }
417
-
418
- // Handle OAuth discovery - indicate this server uses Bearer token auth, not OAuth
419
- if (pathname === '/.well-known/oauth-authorization-server') {
420
- return new Response(
421
- JSON.stringify({
422
- error: 'oauth_not_supported',
423
- error_description: 'This server uses Bearer token authentication, not OAuth. Include your API token in the Authorization header.',
424
- }),
425
- {
426
- status: 404,
427
- headers: { 'Content-Type': 'application/json', ...corsHeaders },
428
- }
429
- );
430
- }
431
-
432
- // Return JSON 404 for all other routes
433
- return new Response(
434
- JSON.stringify({ error: 'not_found', message: 'The requested resource was not found' }),
435
- { status: 404, headers: { 'Content-Type': 'application/json', ...corsHeaders } }
436
- );
437
- },
438
- };