@chnxthr/knowledge-mcp 0.0.0-stage → 0.3.1

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 (190) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -2
  3. package/dist/bootstrap.d.ts +1 -0
  4. package/dist/bootstrap.js +18 -0
  5. package/dist/bootstrap.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +5 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/core/auth.d.ts +38 -0
  10. package/dist/core/auth.js +93 -0
  11. package/dist/core/auth.js.map +1 -0
  12. package/dist/core/config.d.ts +259 -0
  13. package/dist/core/config.js +172 -0
  14. package/dist/core/config.js.map +1 -0
  15. package/dist/core/context.d.ts +25 -0
  16. package/dist/core/context.js +37 -0
  17. package/dist/core/context.js.map +1 -0
  18. package/dist/core/env.d.ts +2 -0
  19. package/dist/core/env.js +5 -0
  20. package/dist/core/env.js.map +1 -0
  21. package/dist/core/log_mask.d.ts +10 -0
  22. package/dist/core/log_mask.js +88 -0
  23. package/dist/core/log_mask.js.map +1 -0
  24. package/dist/core/oauth.d.ts +46 -0
  25. package/dist/core/oauth.js +507 -0
  26. package/dist/core/oauth.js.map +1 -0
  27. package/dist/core/paths.d.ts +5 -0
  28. package/dist/core/paths.js +47 -0
  29. package/dist/core/paths.js.map +1 -0
  30. package/dist/core/scope.d.ts +28 -0
  31. package/dist/core/scope.js +54 -0
  32. package/dist/core/scope.js.map +1 -0
  33. package/dist/core/upstream.d.ts +30 -0
  34. package/dist/core/upstream.js +164 -0
  35. package/dist/core/upstream.js.map +1 -0
  36. package/dist/index.d.ts +23 -0
  37. package/dist/index.js +21 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/plugin/define.d.ts +144 -0
  40. package/dist/plugin/define.js +44 -0
  41. package/dist/plugin/define.js.map +1 -0
  42. package/dist/plugin/load.d.ts +2 -0
  43. package/dist/plugin/load.js +41 -0
  44. package/dist/plugin/load.js.map +1 -0
  45. package/dist/plugin/ping.d.ts +9 -0
  46. package/dist/plugin/ping.js +29 -0
  47. package/dist/plugin/ping.js.map +1 -0
  48. package/dist/plugin/registry.d.ts +62 -0
  49. package/dist/plugin/registry.js +248 -0
  50. package/dist/plugin/registry.js.map +1 -0
  51. package/dist/plugin/result.d.ts +20 -0
  52. package/dist/plugin/result.js +59 -0
  53. package/dist/plugin/result.js.map +1 -0
  54. package/dist/plugins/drive/chunker.d.ts +28 -0
  55. package/dist/plugins/drive/chunker.js +356 -0
  56. package/dist/plugins/drive/chunker.js.map +1 -0
  57. package/dist/plugins/drive/clean_text.d.ts +13 -0
  58. package/dist/plugins/drive/clean_text.js +31 -0
  59. package/dist/plugins/drive/clean_text.js.map +1 -0
  60. package/dist/plugins/drive/client.d.ts +94 -0
  61. package/dist/plugins/drive/client.js +220 -0
  62. package/dist/plugins/drive/client.js.map +1 -0
  63. package/dist/plugins/drive/embeddings.d.ts +15 -0
  64. package/dist/plugins/drive/embeddings.js +118 -0
  65. package/dist/plugins/drive/embeddings.js.map +1 -0
  66. package/dist/plugins/drive/exclude.d.ts +11 -0
  67. package/dist/plugins/drive/exclude.js +36 -0
  68. package/dist/plugins/drive/exclude.js.map +1 -0
  69. package/dist/plugins/drive/extract_text.d.ts +26 -0
  70. package/dist/plugins/drive/extract_text.js +95 -0
  71. package/dist/plugins/drive/extract_text.js.map +1 -0
  72. package/dist/plugins/drive/get_token.d.ts +5 -0
  73. package/dist/plugins/drive/get_token.js +128 -0
  74. package/dist/plugins/drive/get_token.js.map +1 -0
  75. package/dist/plugins/drive/index.d.ts +5 -0
  76. package/dist/plugins/drive/index.js +72 -0
  77. package/dist/plugins/drive/index.js.map +1 -0
  78. package/dist/plugins/drive/indexer.d.ts +69 -0
  79. package/dist/plugins/drive/indexer.js +204 -0
  80. package/dist/plugins/drive/indexer.js.map +1 -0
  81. package/dist/plugins/drive/mathpix.d.ts +4 -0
  82. package/dist/plugins/drive/mathpix.js +102 -0
  83. package/dist/plugins/drive/mathpix.js.map +1 -0
  84. package/dist/plugins/drive/ooxml.d.ts +17 -0
  85. package/dist/plugins/drive/ooxml.js +152 -0
  86. package/dist/plugins/drive/ooxml.js.map +1 -0
  87. package/dist/plugins/drive/path.d.ts +40 -0
  88. package/dist/plugins/drive/path.js +87 -0
  89. package/dist/plugins/drive/path.js.map +1 -0
  90. package/dist/plugins/drive/qdrant.d.ts +123 -0
  91. package/dist/plugins/drive/qdrant.js +148 -0
  92. package/dist/plugins/drive/qdrant.js.map +1 -0
  93. package/dist/plugins/drive/reindex.d.ts +2 -0
  94. package/dist/plugins/drive/reindex.js +263 -0
  95. package/dist/plugins/drive/reindex.js.map +1 -0
  96. package/dist/plugins/drive/settings.d.ts +11 -0
  97. package/dist/plugins/drive/settings.js +13 -0
  98. package/dist/plugins/drive/settings.js.map +1 -0
  99. package/dist/plugins/drive/sync.d.ts +16 -0
  100. package/dist/plugins/drive/sync.js +139 -0
  101. package/dist/plugins/drive/sync.js.map +1 -0
  102. package/dist/plugins/drive/sync_state.d.ts +3 -0
  103. package/dist/plugins/drive/sync_state.js +26 -0
  104. package/dist/plugins/drive/sync_state.js.map +1 -0
  105. package/dist/plugins/drive/tools/list_drive_folder.d.ts +104 -0
  106. package/dist/plugins/drive/tools/list_drive_folder.js +221 -0
  107. package/dist/plugins/drive/tools/list_drive_folder.js.map +1 -0
  108. package/dist/plugins/drive/tools/read_drive_file.d.ts +66 -0
  109. package/dist/plugins/drive/tools/read_drive_file.js +206 -0
  110. package/dist/plugins/drive/tools/read_drive_file.js.map +1 -0
  111. package/dist/plugins/drive/tools/search_drive.d.ts +52 -0
  112. package/dist/plugins/drive/tools/search_drive.js +78 -0
  113. package/dist/plugins/drive/tools/search_drive.js.map +1 -0
  114. package/dist/plugins/drive/tools/semantic_search_drive.d.ts +109 -0
  115. package/dist/plugins/drive/tools/semantic_search_drive.js +228 -0
  116. package/dist/plugins/drive/tools/semantic_search_drive.js.map +1 -0
  117. package/dist/plugins/github/index.d.ts +1 -0
  118. package/dist/plugins/github/index.js +27 -0
  119. package/dist/plugins/github/index.js.map +1 -0
  120. package/dist/plugins/github/repo_tree.d.ts +57 -0
  121. package/dist/plugins/github/repo_tree.js +123 -0
  122. package/dist/plugins/github/repo_tree.js.map +1 -0
  123. package/dist/plugins/index.d.ts +11 -0
  124. package/dist/plugins/index.js +7 -0
  125. package/dist/plugins/index.js.map +1 -0
  126. package/dist/plugins/railway/index.d.ts +1 -0
  127. package/dist/plugins/railway/index.js +21 -0
  128. package/dist/plugins/railway/index.js.map +1 -0
  129. package/dist/plugins/railway/tools.d.ts +114 -0
  130. package/dist/plugins/railway/tools.js +165 -0
  131. package/dist/plugins/railway/tools.js.map +1 -0
  132. package/dist/plugins/slack/api.d.ts +63 -0
  133. package/dist/plugins/slack/api.js +280 -0
  134. package/dist/plugins/slack/api.js.map +1 -0
  135. package/dist/plugins/slack/index.d.ts +1 -0
  136. package/dist/plugins/slack/index.js +80 -0
  137. package/dist/plugins/slack/index.js.map +1 -0
  138. package/dist/plugins/slack/tools/slack_files.d.ts +87 -0
  139. package/dist/plugins/slack/tools/slack_files.js +204 -0
  140. package/dist/plugins/slack/tools/slack_files.js.map +1 -0
  141. package/dist/plugins/slack/tools/slack_list_channels.d.ts +46 -0
  142. package/dist/plugins/slack/tools/slack_list_channels.js +197 -0
  143. package/dist/plugins/slack/tools/slack_list_channels.js.map +1 -0
  144. package/dist/plugins/slack/tools/slack_read_channel.d.ts +56 -0
  145. package/dist/plugins/slack/tools/slack_read_channel.js +150 -0
  146. package/dist/plugins/slack/tools/slack_read_channel.js.map +1 -0
  147. package/dist/plugins/slack/tools/slack_search.d.ts +53 -0
  148. package/dist/plugins/slack/tools/slack_search.js +132 -0
  149. package/dist/plugins/slack/tools/slack_search.js.map +1 -0
  150. package/dist/run.d.ts +8 -0
  151. package/dist/run.js +270 -0
  152. package/dist/run.js.map +1 -0
  153. package/dist/server/http.d.ts +5 -0
  154. package/dist/server/http.js +406 -0
  155. package/dist/server/http.js.map +1 -0
  156. package/dist/server/identity.d.ts +17 -0
  157. package/dist/server/identity.js +47 -0
  158. package/dist/server/identity.js.map +1 -0
  159. package/dist/server/mcp.d.ts +7 -0
  160. package/dist/server/mcp.js +55 -0
  161. package/dist/server/mcp.js.map +1 -0
  162. package/dist/server/stdio.d.ts +2 -0
  163. package/dist/server/stdio.js +40 -0
  164. package/dist/server/stdio.js.map +1 -0
  165. package/dist/store/index.d.ts +11 -0
  166. package/dist/store/index.js +31 -0
  167. package/dist/store/index.js.map +1 -0
  168. package/dist/store/keys.d.ts +9 -0
  169. package/dist/store/keys.js +32 -0
  170. package/dist/store/keys.js.map +1 -0
  171. package/dist/store/sqlite.d.ts +38 -0
  172. package/dist/store/sqlite.js +316 -0
  173. package/dist/store/sqlite.js.map +1 -0
  174. package/dist/store/supabase.d.ts +35 -0
  175. package/dist/store/supabase.js +269 -0
  176. package/dist/store/supabase.js.map +1 -0
  177. package/dist/store/supabase_client.d.ts +10 -0
  178. package/dist/store/supabase_client.js +31 -0
  179. package/dist/store/supabase_client.js.map +1 -0
  180. package/dist/store/tool_log.d.ts +12 -0
  181. package/dist/store/tool_log.js +8 -0
  182. package/dist/store/tool_log.js.map +1 -0
  183. package/dist/store/types.d.ts +96 -0
  184. package/dist/store/types.js +2 -0
  185. package/dist/store/types.js.map +1 -0
  186. package/dist/version.d.ts +1 -0
  187. package/dist/version.js +11 -0
  188. package/dist/version.js.map +1 -0
  189. package/package.json +80 -4
  190. package/sql/supabase.sql +185 -0
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Streamable HTTP transport (default). Express app with:
3
+ * - POST/GET/DELETE /mcp — Authorization: Bearer <API key or OAuth access token>, checked on every request
4
+ * - GET /health — no auth
5
+ * - OAuth 2.1 routes — only when OAuth is configured (oauth.ts)
6
+ *
7
+ * The auth context reaches tool handlers through AsyncLocalStorage (context.ts).
8
+ */
9
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
10
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
11
+ import express from 'express';
12
+ import rateLimit from 'express-rate-limit';
13
+ import { randomUUID, createHash } from 'crypto';
14
+ import { mcpAuthRouter, getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/sdk/server/auth/router.js';
15
+ import { authenticateApiKey } from '../core/auth.js';
16
+ import { authStore } from '../core/context.js';
17
+ import { oauthProvider, isOAuthConfigured, oauthConfigProblems, oauthAttempted, OAUTH_ISSUER_URL, OAUTH_RESOURCE, OAUTH_SCOPES, CONSENT_COOKIE, CONSENT_PATH, } from '../core/oauth.js';
18
+ import { maskedSecretCount } from '../core/log_mask.js';
19
+ import { getConfig, configSource } from '../core/config.js';
20
+ import { getStore } from '../store/index.js';
21
+ import { VERSION } from '../version.js';
22
+ import { resolveUser } from './identity.js';
23
+ import { createMcpServer } from './mcp.js';
24
+ // Claude.ai 웹 커넥터는 브라우저에서 직접 /mcp 를 호출 → CORS 필요. 허용 Origin allowlist(env override).
25
+ const MCP_CORS_ORIGINS = (process.env.MCP_CORS_ORIGINS || 'https://claude.ai,https://claude.com')
26
+ .split(',')
27
+ .map((s) => s.trim())
28
+ .filter(Boolean);
29
+ // act-as 위임 — 이 key_id(내부 프록시 키)들에서만 X-MCP-Act-As-Email 을 신뢰해
30
+ // "로그인 직원 권한"으로 강등/적용한다. 일반 키엔 영향 0. 헤더 role 은 신뢰하지 않고
31
+ // app_users 에서 서버 재검증(위조 차단). 헤더 없으면 viewer 강등(fail-safe).
32
+ const ACT_AS_ALLOWED_KEY_IDS = new Set((process.env.ACT_AS_ALLOWED_KEY_IDS || '')
33
+ .split(',')
34
+ .map((s) => s.trim())
35
+ .filter(Boolean));
36
+ /** /mcp 응답에 CORS 헤더 부착(허용 Origin 일 때만). 비브라우저 클라이언트(Origin 없음)는 무영향. */
37
+ function applyMcpCors(req, res) {
38
+ const origin = req.headers.origin;
39
+ if (origin && MCP_CORS_ORIGINS.includes(origin)) {
40
+ res.setHeader('Access-Control-Allow-Origin', origin);
41
+ res.setHeader('Vary', 'Origin');
42
+ res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, OPTIONS');
43
+ res.setHeader('Access-Control-Allow-Headers', 'authorization, content-type, mcp-session-id, mcp-protocol-version, last-event-id');
44
+ res.setHeader('Access-Control-Expose-Headers', 'WWW-Authenticate, Mcp-Session-Id');
45
+ res.setHeader('Access-Control-Max-Age', '86400');
46
+ }
47
+ }
48
+ /**
49
+ * 고권한 secret 의 fingerprint(sha256 앞 8자)만 부팅 시 로깅 — 값은 절대 노출하지 않는다(S1).
50
+ * 회전 후 "새 키로 떴는지" 운영이 한눈에 확인하는 용도. fingerprint 가 그대로면 회전 미반영.
51
+ */
52
+ function logSecretFingerprints() {
53
+ const KEYS = [
54
+ 'SUPABASE_SERVICE_ROLE_KEY', 'OAUTH_JWT_SECRET',
55
+ 'GITHUB_PAT', 'GITHUB_TOKEN', 'RAILWAY_MCP_TOKEN',
56
+ 'SLACK_USER_TOKEN', 'SLACK_BOT_TOKEN', 'SLACK_TOKEN',
57
+ ];
58
+ const fp = (v) => createHash('sha256').update(v).digest('hex').slice(0, 8);
59
+ const parts = KEYS.filter((k) => process.env[k]).map((k) => `${k}=${fp(process.env[k])}`);
60
+ if (parts.length)
61
+ console.error(`[secrets] fingerprints(sha256/8): ${parts.join(' ')}`);
62
+ }
63
+ /** TRUST_PROXY env → express 'trust proxy' 값. 숫자=홉 수, true/false, 그 밖=서브넷 목록 문자열. */
64
+ function trustProxySetting() {
65
+ const v = (process.env.TRUST_PROXY ?? '1').trim();
66
+ if (/^\d+$/.test(v))
67
+ return Number(v);
68
+ if (v === 'true')
69
+ return true;
70
+ if (v === 'false')
71
+ return false;
72
+ return v;
73
+ }
74
+ export async function runHttp(registry) {
75
+ // 어떤 플러그인이 켜졌는지(자격증명 갖춰진 것만), 어느 저장소를 쓰는지 부팅 시 1줄 로그 — ops 가시성.
76
+ const store = await getStore();
77
+ console.error(`[knowledge-mcp] config=${configSource()} · store=${store.kind} (${store.location}) · plugins · ${registry.summary()}`);
78
+ console.error(`[log-mask] env 시크릿 ${maskedSecretCount()}개 + 일반 패턴 마스킹 중`);
79
+ if (configSource() === 'defaults') {
80
+ console.error('[knowledge-mcp] ⚠️ no config found, running on defaults (no Drive roots, no excluded folders, no login allowlist). ' +
81
+ 'Set MCP_CONFIG_JSON or put mcp.config.json in the working directory (/app/mcp.config.json in the container).');
82
+ }
83
+ logSecretFingerprints(); // 고권한 키 fingerprint(값 비노출) — 회전 검증용 (S1)
84
+ const app = express();
85
+ // 프록시 뒤라면 X-Forwarded-For 를 몇 홉까지 믿을지. 기본 1(Railway·Fly·Cloud Run 같은 앞단 프록시 1개).
86
+ // 프록시 없이 직접 노출하면 TRUST_PROXY=false 로 — 그래야 클라이언트가 보낸 헤더로 IP 를 바꿔 한도를 피하지 못한다.
87
+ app.set('trust proxy', trustProxySetting());
88
+ app.use(express.json({ limit: '4mb' }));
89
+ // Health check — 인증 X
90
+ app.get('/health', (_req, res) => {
91
+ res.json({ ok: true, server: 'knowledge-mcp', version: VERSION });
92
+ });
93
+ // ── OAuth 2.1 (Claude 앱 커스텀 커넥터용) — env 갖춰졌을 때만 (없으면 기존 Bearer 만) ──
94
+ const oauthEnabled = isOAuthConfigured();
95
+ let resourceMetadataUrl;
96
+ if (oauthEnabled) {
97
+ resourceMetadataUrl = getOAuthProtectedResourceMetadataUrl(new URL(OAUTH_RESOURCE));
98
+ // Google 로그인 콜백 — 우리 AS 가 Google(OIDC)로 브리지 후 client 로 우리 code 발급
99
+ app.get('/oauth/google/callback', async (req, res) => {
100
+ const code = typeof req.query.code === 'string' ? req.query.code : '';
101
+ const state = typeof req.query.state === 'string' ? req.query.state : '';
102
+ if (!code || !state) {
103
+ res.status(400).send('missing code/state');
104
+ return;
105
+ }
106
+ try {
107
+ const url = await oauthProvider.completeGoogleLogin(code, state);
108
+ res.redirect(url);
109
+ }
110
+ catch (e) {
111
+ res.status(400).send(`OAuth login error: ${e instanceof Error ? e.message : String(e)}`);
112
+ }
113
+ });
114
+ // AS 엔드포인트 + .well-known(authorize·token·register·AS메타·PRM). 루트에 장착.
115
+ app.use(mcpAuthRouter({
116
+ provider: oauthProvider,
117
+ issuerUrl: new URL(OAUTH_ISSUER_URL),
118
+ scopesSupported: OAUTH_SCOPES,
119
+ resourceName: getConfig().branding.name,
120
+ resourceServerUrl: new URL(OAUTH_RESOURCE),
121
+ }));
122
+ // 동의 화면의 승인/취소 — oauth.ts consumeConsent. 쿠키(SameSite=Lax)와 폼 csrf 가 모두 맞아야 진행.
123
+ app.post(CONSENT_PATH, express.urlencoded({ extended: false, limit: '4kb' }), (req, res) => {
124
+ const body = (req.body ?? {});
125
+ const cookie = String(req.headers.cookie ?? '')
126
+ .split(';')
127
+ .map((c) => c.trim())
128
+ .find((c) => c.startsWith(`${CONSENT_COOKIE}=`));
129
+ const cookieCsrf = cookie ? cookie.slice(CONSENT_COOKIE.length + 1) : '';
130
+ res.setHeader('Cache-Control', 'no-store');
131
+ res.setHeader('Set-Cookie', `${CONSENT_COOKIE}=; Path=${CONSENT_PATH}; Max-Age=0; HttpOnly; SameSite=Lax`);
132
+ try {
133
+ const url = oauthProvider.consumeConsent(String(body.id ?? ''), String(body.csrf ?? ''), cookieCsrf, body.action === 'approve');
134
+ res.redirect(url);
135
+ }
136
+ catch (e) {
137
+ res.status(400).type('text').send(e instanceof Error ? e.message : String(e));
138
+ }
139
+ });
140
+ console.error(`[knowledge-mcp] OAuth enabled · issuer=${OAUTH_ISSUER_URL} · resource=${OAUTH_RESOURCE}`);
141
+ }
142
+ else if (oauthAttempted()) {
143
+ // 반쯤 설정된 상태는 조용히 끄지 않고 빠진 값을 이름으로 알린다(값은 노출 안 함).
144
+ console.error(`[knowledge-mcp] ⚠️ OAuth off — missing: ${oauthConfigProblems().join(', ')}`);
145
+ }
146
+ else {
147
+ console.error('[knowledge-mcp] OAuth 미설정 (Bearer 전용)');
148
+ }
149
+ // CORS preflight — Claude.ai 웹 커넥터 브라우저 요청용.
150
+ app.options('/mcp', (req, res) => {
151
+ applyMcpCors(req, res);
152
+ res.status(204).end();
153
+ });
154
+ // GET(SSE 상시 스트림)·DELETE(세션 종료) 는 아래 rate-limit 정의 후 세션 라우팅 핸들러로 등록한다
155
+ // (하이브리드 stateful — 활성 세션이 있을 때만 그 transport 로 처리, 없으면 405).
156
+ // ── /mcp rate-limit (S2) ── IP 기준(trust proxy 1 로 req.ip 정확). 한도는 env override.
157
+ // 목적: 잘못된 키 폭주가 validate_mcp_key RPC 를 증폭(DB 부하·비용)시키는 것 차단 + 일반 abuse 완화.
158
+ // 2단: ① 전체 넉넉한 한도(정상 챗봇/IDE 무영향) ② 인증 실패(401)에만 더 강한 한도.
159
+ const RL_WINDOW_MS = Number(process.env.MCP_RATE_LIMIT_WINDOW_MS ?? 60_000);
160
+ const rlHandler = (status, message) => (req, res) => {
161
+ applyMcpCors(req, res);
162
+ res.status(status).json({ jsonrpc: '2.0', error: { code: -32029, message }, id: null });
163
+ };
164
+ const mcpRateLimit = rateLimit({
165
+ windowMs: RL_WINDOW_MS,
166
+ limit: Number(process.env.MCP_RATE_LIMIT_MAX ?? 120),
167
+ standardHeaders: 'draft-7',
168
+ legacyHeaders: false,
169
+ handler: rlHandler(429, 'rate limit exceeded — slow down'),
170
+ });
171
+ const mcpAuthFailRateLimit = rateLimit({
172
+ windowMs: RL_WINDOW_MS,
173
+ limit: Number(process.env.MCP_RATE_LIMIT_FAIL_MAX ?? 20),
174
+ // 401 만 센다 — 재배포 뒤 세션 만료(404), 세션 없는 GET(405), 끊긴 SSE 까지 "인증 실패"로 세면
175
+ // 같은 출구 IP(사내 NAT 등)의 정상 사용자가 함께 429 를 받는다.
176
+ skipSuccessfulRequests: true,
177
+ requestWasSuccessful: (_req, res) => res.statusCode !== 401,
178
+ standardHeaders: 'draft-7',
179
+ legacyHeaders: false,
180
+ handler: rlHandler(429, 'too many failed auth attempts — slow down'),
181
+ });
182
+ // ── 인증 해석 (POST·GET·DELETE 공용) ──
183
+ // 세션 방식이어도 인증은 매 요청 수행한다(권한을 세션에 묶지 않음 — 세션ID 가 유출돼도 각 요청의
184
+ // Bearer/OAuth 토큰으로 재인증하므로 세션 하이재킹으로 권한 상승 불가). ok:false 면 send401.
185
+ const extractKey = (req) => {
186
+ const authHeader = req.headers.authorization;
187
+ const key = authHeader?.startsWith('Bearer ') ? authHeader.slice(7).trim() : undefined;
188
+ return { key, looksJwt: !!key && key.split('.').length === 3 };
189
+ };
190
+ const resolveAuth = async (req) => {
191
+ const { key, looksJwt } = extractKey(req);
192
+ let auth;
193
+ // dual-auth (순서 최적화): OAuth access token 은 JWT(점 3개) → 로컬 HS256 검증을 *먼저*.
194
+ // 기존 mcp_key/dev key 는 JWT 형식 아니라 충돌 없음. 커넥터(OAuth) 호출이 validate_mcp_key RPC 를
195
+ // 헛왕복(매번 실패, 캐시 안 됨)하지 않게 한다 — 호출 지연·DB 부하 제거.
196
+ if (oauthEnabled && looksJwt) {
197
+ try {
198
+ const info = await oauthProvider.verifyAccessToken(key);
199
+ const oauthEmail = typeof info.extra?.email === 'string' ? info.extra.email : undefined;
200
+ // OAuth 사용자도 app_users role 반영(act-as 와 일관). email 은 Google 로그인+allowlist 검증값이라
201
+ // 신뢰. 없음/미등록/조회 실패 → viewer (P2 — 미등록 계정에 PII 도구 미개방).
202
+ let oauthRole = 'viewer';
203
+ let oauthScopes;
204
+ if (oauthEmail) {
205
+ const r = await resolveUser(info.clientId ?? 'oauth', oauthEmail);
206
+ oauthRole = r.role;
207
+ oauthScopes = r.scopes;
208
+ }
209
+ auth = {
210
+ ok: true, org_id: getConfig().orgId, role: oauthRole,
211
+ email: oauthEmail, key_id: info.clientId, scopes: oauthScopes,
212
+ };
213
+ }
214
+ catch {
215
+ auth = { ok: false, reason: 'invalid oauth token' };
216
+ }
217
+ }
218
+ else {
219
+ // 기존 Bearer 경로(IDE·내부 프록시·dev key)
220
+ auth = await authenticateApiKey(key);
221
+ // 직접 키(IDE)도 직원 email 로 role+scope 를 app_users 에서 재계산(act-as·OAuth 와 통일).
222
+ // P1: role 도 재계산해 퇴사·강등 즉시 반영(옛 구멍: revoke 전까지 admin 유지). dev key 는 제외.
223
+ if (auth.ok && auth.email && auth.email !== 'dev@local') {
224
+ const { role, scopes } = await resolveUser(auth.key_id ?? 'direct', auth.email);
225
+ auth = { ...auth, role, scopes };
226
+ }
227
+ }
228
+ if (!auth.ok)
229
+ return auth;
230
+ // ── act-as (내부 프록시 → 로그인 직원 권한 전파) ──
231
+ // 위임 key_id 일 때만. 헤더 role 은 안 읽고 app_users 서버 재계산(위조 방지). 헤더 없으면 viewer.
232
+ if (auth.key_id && ACT_AS_ALLOWED_KEY_IDS.has(auth.key_id)) {
233
+ const hdr = req.headers['x-mcp-act-as-email'];
234
+ const actAsEmail = typeof hdr === 'string' ? hdr.trim() : '';
235
+ if (!actAsEmail) {
236
+ auth = { ...auth, role: 'viewer', email: undefined, actAs: true };
237
+ }
238
+ else {
239
+ const { role, scopes } = await resolveUser(auth.key_id, actAsEmail);
240
+ auth = { ...auth, role, email: actAsEmail, scopes, actAs: true };
241
+ console.error(`[act-as] key_id=${auth.key_id} email=${actAsEmail} role=${role}`);
242
+ }
243
+ }
244
+ return auth;
245
+ };
246
+ const send401 = (req, res, auth) => {
247
+ const { key, looksJwt } = extractKey(req);
248
+ // OAuth 진입 조건(토큰 없음 또는 JWT)에만 discovery 트리거 헤더 부착(IDE 오유입 방지).
249
+ if (oauthEnabled && resourceMetadataUrl && (!key || looksJwt)) {
250
+ res.setHeader('WWW-Authenticate', `Bearer resource_metadata="${resourceMetadataUrl}"`);
251
+ }
252
+ res.status(401).json({
253
+ jsonrpc: '2.0',
254
+ error: { code: -32001, message: `auth failed: ${auth.reason ?? 'unauthorized'}` },
255
+ id: null,
256
+ });
257
+ };
258
+ const sessions = new Map();
259
+ const SESSION_IDLE_MS = Number(process.env.MCP_SESSION_IDLE_MS ?? 30 * 60_000);
260
+ const SESSION_MAX = Number(process.env.MCP_SESSION_MAX ?? 1000);
261
+ const dropSession = (sid) => {
262
+ const s = sessions.get(sid);
263
+ if (s) {
264
+ sessions.delete(sid);
265
+ s.transport.close().catch(() => { });
266
+ }
267
+ };
268
+ // 유휴 세션 청소 — DELETE 없이 사라진 커넥터의 세션을 만료(메모리 누수 방지). unref → 프로세스 유지 안 함.
269
+ const sweeper = setInterval(() => {
270
+ const now = Date.now();
271
+ for (const [sid, s] of sessions)
272
+ if (now - s.lastActive > SESSION_IDLE_MS)
273
+ dropSession(sid);
274
+ }, 5 * 60_000);
275
+ sweeper.unref();
276
+ // GET(SSE 상시 스트림)·DELETE(세션 종료) — 활성 세션이 있을 때만 그 transport 로 라우팅.
277
+ // 세션 없으면 stateless 서버엔 스트림이 없으므로 405(기존 동작 유지).
278
+ const handleSessionStream = async (req, res) => {
279
+ applyMcpCors(req, res);
280
+ const auth = await resolveAuth(req);
281
+ if (!auth.ok)
282
+ return send401(req, res, auth);
283
+ const sidHeader = req.headers['mcp-session-id'];
284
+ const sid = typeof sidHeader === 'string' ? sidHeader : undefined;
285
+ const s = sid ? sessions.get(sid) : undefined;
286
+ if (!s) {
287
+ res.setHeader('Allow', 'POST, OPTIONS');
288
+ res.status(405).json({
289
+ jsonrpc: '2.0',
290
+ error: { code: -32000, message: 'no active session for this stream' },
291
+ id: null,
292
+ });
293
+ return;
294
+ }
295
+ s.lastActive = Date.now();
296
+ try {
297
+ await authStore.run(auth, () => s.transport.handleRequest(req, res));
298
+ }
299
+ catch (e) {
300
+ console.error('[knowledge-mcp] GET/DELETE handler error:', e);
301
+ if (!res.headersSent)
302
+ res.status(500).end();
303
+ }
304
+ };
305
+ app.get('/mcp', mcpRateLimit, mcpAuthFailRateLimit, handleSessionStream);
306
+ app.delete('/mcp', mcpRateLimit, mcpAuthFailRateLimit, handleSessionStream);
307
+ // MCP endpoint (POST) — 세션 라우팅 + stateless 폴백
308
+ app.post('/mcp', mcpRateLimit, mcpAuthFailRateLimit, async (req, res) => {
309
+ applyMcpCors(req, res);
310
+ const auth = await resolveAuth(req);
311
+ if (!auth.ok)
312
+ return send401(req, res, auth);
313
+ const sidHeader = req.headers['mcp-session-id'];
314
+ const sid = typeof sidHeader === 'string' ? sidHeader : undefined;
315
+ try {
316
+ // 1) 기존 세션 → 그 transport 로 라우팅 (인증은 위에서 매 요청 수행)
317
+ if (sid) {
318
+ const s = sessions.get(sid);
319
+ if (!s) {
320
+ // 만료/미지 세션 — 클라이언트가 재init 하도록 404
321
+ res.status(404).json({
322
+ jsonrpc: '2.0',
323
+ error: { code: -32001, message: 'session not found — reinitialize' },
324
+ id: null,
325
+ });
326
+ return;
327
+ }
328
+ s.lastActive = Date.now();
329
+ await authStore.run(auth, () => s.transport.handleRequest(req, res, req.body));
330
+ return;
331
+ }
332
+ // 2) initialize (세션ID 없음) → 새 stateful 세션 생성 (Mcp-Session-Id 발급)
333
+ if (isInitializeRequest(req.body)) {
334
+ if (sessions.size >= SESSION_MAX) {
335
+ // 백스톱 — 가장 오래된 세션 정리 (무한 증가 방지)
336
+ let oldestSid;
337
+ let oldest = Infinity;
338
+ for (const [k, v] of sessions)
339
+ if (v.lastActive < oldest) {
340
+ oldest = v.lastActive;
341
+ oldestSid = k;
342
+ }
343
+ if (oldestSid)
344
+ dropSession(oldestSid);
345
+ }
346
+ const transport = new StreamableHTTPServerTransport({
347
+ sessionIdGenerator: () => randomUUID(),
348
+ onsessioninitialized: (newSid) => {
349
+ sessions.set(newSid, { transport, lastActive: Date.now() });
350
+ },
351
+ });
352
+ transport.onclose = () => {
353
+ if (transport.sessionId)
354
+ sessions.delete(transport.sessionId);
355
+ };
356
+ const server = createMcpServer(registry);
357
+ await server.connect(transport);
358
+ await authStore.run(auth, () => transport.handleRequest(req, res, req.body));
359
+ return;
360
+ }
361
+ // 3) stateless 폴백 (비-initialize + 세션ID 없음) — 기존 IDE·프록시 one-shot 보존.
362
+ const server = createMcpServer(registry);
363
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
364
+ res.on('close', () => {
365
+ transport.close().catch(() => { });
366
+ });
367
+ await server.connect(transport);
368
+ await authStore.run(auth, () => transport.handleRequest(req, res, req.body));
369
+ }
370
+ catch (e) {
371
+ const msg = e instanceof Error ? e.message : String(e);
372
+ console.error(`[knowledge-mcp] /mcp handler error:`, e);
373
+ if (!res.headersSent) {
374
+ res.status(500).json({
375
+ jsonrpc: '2.0',
376
+ error: { code: -32603, message: msg },
377
+ id: null,
378
+ });
379
+ }
380
+ }
381
+ });
382
+ // PORT=0 은 빈 포트를 고른다(시험용). 실제 포트는 반환값으로 알린다.
383
+ const wanted = parseInt(process.env.PORT || '8080', 10);
384
+ const httpServer = await new Promise((resolveListen, rejectListen) => {
385
+ const srv = app.listen(wanted, () => resolveListen(srv));
386
+ srv.once('error', rejectListen);
387
+ });
388
+ const address = httpServer.address();
389
+ const port = typeof address === 'object' && address ? address.port : wanted;
390
+ console.error(`[knowledge-mcp] HTTP mode · listening on :${port} · version=${VERSION}`);
391
+ // 외부 연결은 포트를 연 뒤 백그라운드로 — upstream·Qdrant 가 응답하지 않아도 /health 가 먼저 뜬다
392
+ // (예전엔 listen 전에 기다려 healthcheck 시간 안에 포트가 안 열리고 재시작이 반복될 수 있었다).
393
+ // 원격 MCP 는 연결되기 전 tools/list 에서 빠져 있다가, 연결되면 다음 목록부터 나온다.
394
+ void registry.start();
395
+ return {
396
+ port,
397
+ close: () => new Promise((done) => {
398
+ clearInterval(sweeper);
399
+ for (const sid of [...sessions.keys()])
400
+ dropSession(sid);
401
+ httpServer.close(() => done());
402
+ httpServer.closeAllConnections?.();
403
+ }),
404
+ };
405
+ }
406
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/server/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Effective role and scopes of a person, recomputed from app_users on every request path
3
+ * (direct key with an email, OAuth, act-as). Demotions and removals take effect within the cache TTL.
4
+ */
5
+ import type { Role, ResourceScopes } from '../core/auth.js';
6
+ import type { UserRecord } from '../store/types.js';
7
+ /** app_users row → role. No row = viewer. 'admin' = admin. Anything else = member. */
8
+ export declare function roleFromUser(row: Pick<UserRecord, 'role'> | null | undefined): Role;
9
+ /**
10
+ * Role + scopes for `email`. Never trusts a role sent by the client.
11
+ * A failed lookup does not widen access: the result is viewer and is not cached, so a short outage
12
+ * does not stick for the TTL (fail-closed).
13
+ */
14
+ export declare function resolveUser(keyId: string, email: string): Promise<{
15
+ role: Role;
16
+ scopes?: ResourceScopes;
17
+ }>;
@@ -0,0 +1,47 @@
1
+ import { getStore } from '../store/index.js';
2
+ const ACT_AS_TTL_MS = Number(process.env.ACT_AS_CACHE_TTL_MS ?? 60_000);
3
+ const _cache = new Map();
4
+ /** app_users row → role. No row = viewer. 'admin' = admin. Anything else = member. */
5
+ export function roleFromUser(row) {
6
+ if (!row)
7
+ return 'viewer';
8
+ return row.role === 'admin' ? 'admin' : 'member';
9
+ }
10
+ /**
11
+ * Role + scopes for `email`. Never trusts a role sent by the client.
12
+ * A failed lookup does not widen access: the result is viewer and is not cached, so a short outage
13
+ * does not stick for the TTL (fail-closed).
14
+ */
15
+ export async function resolveUser(keyId, email) {
16
+ const cacheKey = `${keyId}:${email.toLowerCase()}`;
17
+ const now = Date.now();
18
+ const cached = _cache.get(cacheKey);
19
+ if (cached && cached.expiresAt > now)
20
+ return { role: cached.role, scopes: cached.scopes };
21
+ let role;
22
+ let scopes;
23
+ try {
24
+ const row = await (await getStore()).getUser(email);
25
+ role = roleFromUser(row);
26
+ scopes =
27
+ row && row.scopesKnown !== false
28
+ ? {
29
+ slackChannels: row.allowedSlackChannels ?? null,
30
+ driveFolders: row.allowedDriveFolders ?? null,
31
+ tools: row.allowedTools ?? null,
32
+ }
33
+ : undefined;
34
+ }
35
+ catch (e) {
36
+ console.error('[identity] app_users lookup failed → viewer:', e instanceof Error ? e.message : e);
37
+ return { role: 'viewer' };
38
+ }
39
+ if (_cache.size > 500) {
40
+ for (const [k, v] of _cache)
41
+ if (v.expiresAt <= now)
42
+ _cache.delete(k);
43
+ }
44
+ _cache.set(cacheKey, { role, scopes, expiresAt: now + ACT_AS_TTL_MS });
45
+ return { role, scopes };
46
+ }
47
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/server/identity.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG7C,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,MAAM,CAAC,CAAC;AACxE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsE,CAAC;AAE7F,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,GAAgD;IAC3E,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,OAAO,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAa,EAAE,KAAa;IAC5D,MAAM,QAAQ,GAAG,GAAG,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAE1F,IAAI,IAAU,CAAC;IACf,IAAI,MAAkC,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QACzB,MAAM;YACJ,GAAG,IAAI,GAAG,CAAC,WAAW,KAAK,KAAK;gBAC9B,CAAC,CAAC;oBACE,aAAa,EAAE,GAAG,CAAC,oBAAoB,IAAI,IAAI;oBAC/C,YAAY,EAAE,GAAG,CAAC,mBAAmB,IAAI,IAAI;oBAC7C,KAAK,EAAE,GAAG,CAAC,YAAY,IAAI,IAAI;iBAChC;gBACH,CAAC,CAAC,SAAS,CAAC;IAClB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClG,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC;QACtB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM;YAAE,IAAI,CAAC,CAAC,SAAS,IAAI,GAAG;gBAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,aAAa,EAAE,CAAC,CAAC;IACvE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * One MCP server instance (per HTTP session, or one for stdio). Lists and routes tools from the registry,
3
+ * filtered by the caller's permission, and logs every call.
4
+ */
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { type Registry } from '../plugin/registry.js';
7
+ export declare function createMcpServer(registry: Registry): Server;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * One MCP server instance (per HTTP session, or one for stdio). Lists and routes tools from the registry,
3
+ * filtered by the caller's permission, and logs every call.
4
+ */
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
7
+ import { getAuthMaybe } from '../core/context.js';
8
+ import { isToolAllowed } from '../core/scope.js';
9
+ import { logToolCall } from '../store/tool_log.js';
10
+ import { VERSION } from '../version.js';
11
+ import { PING } from '../plugin/registry.js';
12
+ import { handlePing, pingTool } from '../plugin/ping.js';
13
+ import { errorResult, textResult } from '../plugin/result.js';
14
+ export function createMcpServer(registry) {
15
+ const server = new Server({ name: 'knowledge-mcp', version: VERSION }, { capabilities: { tools: {} }, instructions: registry.instructions() });
16
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
17
+ // Plugins without their credentials are already left out, so the model never tries a tool that can't work.
18
+ const tools = registry.listTools(pingTool);
19
+ const auth = getAuthMaybe();
20
+ if (!auth || !auth.ok)
21
+ return { tools: tools.filter((t) => t.name === PING) };
22
+ return { tools: tools.filter((t) => isToolAllowed(auth, t.name, registry.groupOf(t.name))) };
23
+ });
24
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
25
+ const { name, arguments: args } = request.params;
26
+ const t0 = Date.now();
27
+ const auth = getAuthMaybe();
28
+ const result = await (async () => {
29
+ // Checked again on call, not only on list (defense in depth). No auth = deny.
30
+ if (!auth || !auth.ok || !isToolAllowed(auth, name, registry.groupOf(name))) {
31
+ return textResult(`[forbidden] 도구 '${name}' 은 현재 직원 권한으로 접근 불가입니다.`, true);
32
+ }
33
+ if (name === PING) {
34
+ try {
35
+ return handlePing(registry, auth, args);
36
+ }
37
+ catch (e) {
38
+ return errorResult(name, e);
39
+ }
40
+ }
41
+ return registry.call(name, args, auth);
42
+ })();
43
+ const r = result;
44
+ logToolCall({
45
+ tool: name,
46
+ auth,
47
+ ok: r.isError !== true,
48
+ error: r.isError === true ? (r.content?.find((c) => c.type === 'text')?.text ?? null) : null,
49
+ durationMs: Date.now() - t0,
50
+ });
51
+ return result;
52
+ });
53
+ return server;
54
+ }
55
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/server/mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Registry } from '../plugin/registry.js';
2
+ export declare function runStdio(registry: Registry): Promise<void>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * stdio transport — the MCP client starts this process and talks over stdin/stdout. One process = one user.
3
+ *
4
+ * Without MCP_API_KEY the process runs as the local admin: whoever can start it already holds every token
5
+ * in its env, so a key adds no boundary. With MCP_API_KEY the key is checked like on HTTP, and a key tied
6
+ * to an email gets that person's role and scopes from app_users.
7
+ *
8
+ * stdout carries the protocol. Log only with console.error.
9
+ */
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import { authenticateApiKey } from '../core/auth.js';
12
+ import { setProcessAuth } from '../core/context.js';
13
+ import { getConfig, configSource } from '../core/config.js';
14
+ import { resolveUser } from './identity.js';
15
+ import { createMcpServer } from './mcp.js';
16
+ export async function runStdio(registry) {
17
+ const key = process.env.MCP_API_KEY?.trim();
18
+ let auth;
19
+ if (!key) {
20
+ auth = { ok: true, org_id: getConfig().orgId, role: 'admin', email: 'local@stdio' };
21
+ }
22
+ else {
23
+ auth = await authenticateApiKey(key);
24
+ if (!auth.ok) {
25
+ console.error(`[knowledge-mcp] stdio auth failed: ${auth.reason}. Check MCP_API_KEY, or unset it to run as the local admin.`);
26
+ process.exit(1);
27
+ }
28
+ if (auth.email && auth.email !== 'dev@local') {
29
+ const { role, scopes } = await resolveUser(auth.key_id ?? 'direct', auth.email);
30
+ auth = { ...auth, role, scopes };
31
+ }
32
+ }
33
+ // Set before the transport exists — see context.ts for why AsyncLocalStorage.enterWith is not enough.
34
+ setProcessAuth(auth);
35
+ void registry.start({ jobs: false }); // remotes only — background jobs belong to the long-running HTTP server
36
+ const server = createMcpServer(registry);
37
+ await server.connect(new StdioServerTransport());
38
+ console.error(`[knowledge-mcp] stdio mode · ${auth.email ?? auth.org_id} (${auth.role}) · config=${configSource()} · ${registry.summary()}`);
39
+ }
40
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/server/stdio.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAoB,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAkB;IAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IAC5C,IAAI,IAAiB,CAAC;IACtB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,IAAI,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IACtF,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,sCAAsC,IAAI,CAAC,MAAM,6DAA6D,CAAC,CAAC;YAC9H,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YAC7C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YAChF,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACnC,CAAC;IACH,CAAC;IACD,sGAAsG;IACtG,cAAc,CAAC,IAAI,CAAC,CAAC;IACrB,KAAK,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,wEAAwE;IAC9G,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CACX,gCAAgC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,cAAc,YAAY,EAAE,MAAM,QAAQ,CAAC,OAAO,EAAE,EAAE,CAC9H,CAAC;AACJ,CAAC"}