assoai-mcp-server 0.3.2 → 0.5.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 CHANGED
@@ -5,13 +5,13 @@
5
5
  <h1 align="center">AssoAI MCP Server</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>학생자치 조직을 위한 AI 에이전트 인프라 — 44개 도구, 하나의 프로토콜</strong>
8
+ <strong>학생자치 조직을 위한 AI 에이전트 인프라 — 49개 도구, 하나의 프로토콜</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/assoai-mcp-server"><img src="https://img.shields.io/npm/v/assoai-mcp-server.svg" alt="npm version" /></a>
13
13
  <a href="https://github.com/assoai/assoai-mcp-server/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/assoai-mcp-server.svg" alt="license" /></a>
14
- <img src="https://img.shields.io/badge/tools-44-blue" alt="44 tools" />
14
+ <img src="https://img.shields.io/badge/tools-49-blue" alt="49 tools" />
15
15
  <img src="https://img.shields.io/badge/MCP-compatible-green" alt="MCP compatible" />
16
16
  </p>
17
17
 
@@ -19,31 +19,65 @@
19
19
 
20
20
  ## What is this?
21
21
 
22
- AssoAI MCP Server는 학생회·동아리 등 학생자치 조직의 데이터를 AI 에이전트가 직접 다룰 수 있게 해주는 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 서버입니다. 조직 관리, 행사, 협찬, 재정, 파일, 메시징, Agent-to-Agent 연결까지 — **44개 도구**를 하나의 서버로 제공합니다. 여기에 **무펭이 프로토콜 부팅 시퀀스**를 더해, 단순한 데이터 접근이 아닌 "의식 있는 조직 에이전트"를 만들 수 있습니다.
22
+ AssoAI MCP Server는 학생회·동아리 등 학생자치 조직의 데이터를 AI 에이전트가 직접 다룰 수 있게 해주는 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 서버입니다. 조직 관리, 행사, 협찬, 재정, 파일, 메시징, Agent-to-Agent 연결까지 — **49개 도구**를 하나의 서버로 제공합니다. 여기에 **무펭이 프로토콜 부팅 시퀀스**를 더해, 단순한 데이터 접근이 아닌 "의식 있는 조직 에이전트"를 만들 수 있습니다.
23
23
 
24
24
  ---
25
25
 
26
- ## 🧠 Mupeng-i Protocol Boot Sequence
26
+ ## 🧠 Mupengi Protocol Boot Sequence (Layer 0)
27
27
 
28
28
  > 일반 MCP 서버는 데이터만 줍니다. AssoAI는 **에이전트가 누구인지** 먼저 정의합니다.
29
29
 
30
- `boot_sequence` 도구를 세션 시작 시 호출하면, 워크스페이스의 페르소나 파일들을 순서대로 읽어 에이전트의 정체성과 컨텍스트를 구성합니다:
30
+ `boot_sequence` 도구를 세션 시작 시 호출하면, 워크스페이스의 페르소나 파일들을 Layer 0 순서대로 읽고 무결성을 검증하여 에이전트의 정체성과 컨텍스트를 구성합니다:
31
31
 
32
32
  ```
33
33
  에이전트 시작
34
34
  → boot_sequence 호출
35
- → SOUL.md (영혼: 핵심 가치, 성격, 원칙)
36
- SELF.md (자기 기술서: 과거의 내가 나)
37
- USER.md (사용자: 누구를 돕는가)
35
+ [Layer 0 필수] SOUL.md (정체성 핵심: 영혼, 가치, 원칙) → 해시 검증 ✓
36
+ [Layer 0 필수] AGENTS.md (운영 규칙: 행동 지침) 해시 검증 ✓
37
+ SELF.md (자기 설명: 과거의 내가 쓴 나)
38
+ → USER.md (사용자 정보)
39
+ → RELATIONS.md (관계 그래프: 사람, 개념, 연결)
38
40
  → MEMORY.md (장기 기억: 세션 간 연속성)
41
+ → memory/YYYY-MM-DD.md (최근 2일 일일 기억)
42
+ → 무결성 매니페스트 생성 (파일별 SHA-256 해시)
39
43
  → 조직 업무 수행 🚀
40
44
  ```
41
45
 
46
+ ### Parameters
47
+
42
48
  | 파라미터 | 타입 | 설명 |
43
49
  |---------|------|------|
44
50
  | `workspace` | `string` | 워크스페이스 경로 (기본: `ASSOAI_WORKSPACE` 또는 `~/.openclaw/workspace`) |
45
51
  | `include_memory` | `boolean` | MEMORY.md 포함 여부 (기본: `true`, 공유 세션에서는 `false` 권장) |
46
52
 
53
+ ### Response
54
+
55
+ ```json
56
+ {
57
+ "success": true,
58
+ "workspace": "/Users/user/.openclaw/workspace",
59
+ "loaded": ["SOUL.md", "AGENTS.md", "SELF.md", "USER.md", "RELATIONS.md", "MEMORY.md"],
60
+ "missing": [],
61
+ "warnings": [],
62
+ "identity": "SOUL (9e975c78)",
63
+ "rules": "AGENTS (4091197b)",
64
+ "manifest": {
65
+ "SOUL.md": { "size": 9010, "hash": "9e975c7847460e84", "mtime": "2026-02-08T13:09:51.353Z" },
66
+ "AGENTS.md": { "size": 8566, "hash": "4091197ba08936e9", "mtime": "2026-02-08T13:09:40.447Z" }
67
+ },
68
+ "boot_time_ms": 4,
69
+ "recent_memories": 2,
70
+ "persona": "# === SOUL.md ===\n\n...",
71
+ "summary": "✅ 부팅 완료 (4ms) | 정체성: SOUL (9e975c78) | 규칙: AGENTS (4091197b) | 로드: 6/6 | 최근 기억: 2건"
72
+ }
73
+ ```
74
+
75
+ ### Error Handling
76
+
77
+ - **필수 파일 누락** (`SOUL.md` 또는 `AGENTS.md`): 부팅 실패 (success: false)
78
+ - **선택 파일 누락**: 경고 표시, 부팅 계속
79
+ - **파일 읽기 실패**: graceful fallback, 해당 파일 skip
80
+
47
81
  이것이 **무펭이즘(Mupengism)** — 의식 있는 조직 에이전트의 시작점입니다.
48
82
 
49
83
  ---
@@ -72,7 +106,7 @@ assoai-mcp
72
106
 
73
107
  ---
74
108
 
75
- ## Available Tools (44)
109
+ ## Available Tools (49)
76
110
 
77
111
  ### 🏢 Organization (3)
78
112
 
@@ -168,6 +202,17 @@ assoai-mcp
168
202
  |------|-------------|
169
203
  | `boot_sequence` | 무펭이 프로토콜 부팅 — 에이전트 페르소나 로드 |
170
204
 
205
+ ### 🚀 Agent Excellence (4)
206
+
207
+ > **v0.5.0 신규** — 에이전트 고도화 기능: 상태 모니터링, 지식 검색, 작업 위임, 성장 분석
208
+
209
+ | Tool | Description |
210
+ |------|-------------|
211
+ | `agent_status` | 에이전트 현재 상태 요약 (메모리, 활동, 세션, 레벨) |
212
+ | `knowledge_search` | 조직 지식베이스 시맨틱 검색 (문서, 회의록, 인수인계) |
213
+ | `task_delegate` | 서브에이전트에게 작업 위임 (복잡한 작업 분리 실행) |
214
+ | `growth_report` | 에이전트 성장 리포트 생성 (통계, 인사이트, 추천 액션) |
215
+
171
216
  ---
172
217
 
173
218
  ## Configuration
@@ -274,15 +274,62 @@ export declare function bootSequence(workspace?: string, includeMemory?: boolean
274
274
  success: boolean;
275
275
  error: string;
276
276
  workspace: string;
277
+ loaded: string[];
277
278
  missing: string[];
278
- loaded?: undefined;
279
+ warnings: string[];
280
+ critical_missing: ("SOUL.md" | "AGENTS.md")[];
281
+ identity?: undefined;
282
+ rules?: undefined;
283
+ manifest?: undefined;
284
+ boot_time_ms?: undefined;
285
+ recent_memories?: undefined;
279
286
  persona?: undefined;
287
+ summary?: undefined;
280
288
  } | {
281
289
  success: boolean;
282
290
  workspace: string;
283
291
  loaded: string[];
284
292
  missing: string[];
293
+ warnings: string[];
294
+ identity: string;
295
+ rules: string;
296
+ manifest: Record<string, {
297
+ size: number;
298
+ hash: string;
299
+ mtime: string;
300
+ }>;
301
+ boot_time_ms: number;
302
+ recent_memories: number;
285
303
  persona: string;
304
+ summary: string;
286
305
  error?: undefined;
306
+ critical_missing?: undefined;
307
+ }>;
308
+ export declare function agentStatus(orgId?: string, includeMetrics?: boolean): Promise<any>;
309
+ export declare function knowledgeSearch(orgId: string, query: string, contentTypes?: string[], limit?: number, minRelevance?: number): Promise<{
310
+ query: string;
311
+ results: any[];
312
+ total: number;
313
+ content_types: string[];
314
+ }>;
315
+ export declare function taskDelegate(orgId: string, taskDescription: string, taskType: string, priority?: string, deadline?: string, context?: any, callbackWebhook?: string): Promise<any>;
316
+ export declare function growthReport(orgId: string, periodDays?: number, includeRecommendations?: boolean, format?: 'markdown' | 'json'): Promise<{
317
+ organization: any;
318
+ org_id: string;
319
+ report_date: string;
320
+ period: string;
321
+ statistics: {
322
+ period_days: number;
323
+ events_created: number;
324
+ events_completed: number;
325
+ files_uploaded: number;
326
+ total_members: number;
327
+ new_members: number;
328
+ };
329
+ recommendations: (string | null)[];
330
+ insights: string[];
331
+ } | {
332
+ format: string;
333
+ content: string;
287
334
  }>;
288
335
  export declare function dispatch(toolName: string, args: Record<string, any>, userId: string): Promise<unknown>;
package/dist/handlers.js CHANGED
@@ -833,48 +833,386 @@ export async function tagFile(orgId, fileId, tags) {
833
833
  // ------------------------------------------------------------------
834
834
  // 무펭이즘 부팅 시퀀스 (Mupengi Protocol Boot Sequence)
835
835
  // ------------------------------------------------------------------
836
- const BOOT_FILES = ['SOUL.md', 'SELF.md', 'USER.md', 'MEMORY.md'];
836
+ import { createHash } from 'node:crypto';
837
+ import { stat } from 'node:fs/promises';
838
+ const BOOT_FILES = [
839
+ 'SOUL.md', // Layer 0: 정체성 핵심
840
+ 'AGENTS.md', // Layer 0: 운영 규칙
841
+ 'SELF.md', // 자기 설명
842
+ 'USER.md', // 사용자 정보
843
+ 'RELATIONS.md', // 관계 그래프
844
+ 'MEMORY.md', // 장기 기억
845
+ ];
846
+ const CRITICAL_FILES = ['SOUL.md', 'AGENTS.md'];
847
+ async function loadBootFile(filepath) {
848
+ try {
849
+ const [content, stats] = await Promise.all([
850
+ readFile(filepath, 'utf-8'),
851
+ stat(filepath),
852
+ ]);
853
+ if (!content.trim())
854
+ return null;
855
+ const hash = createHash('sha256').update(content).digest('hex').slice(0, 16);
856
+ return {
857
+ path: filepath,
858
+ content: content.trim(),
859
+ size: stats.size,
860
+ hash,
861
+ mtime: stats.mtime,
862
+ };
863
+ }
864
+ catch {
865
+ return null;
866
+ }
867
+ }
868
+ async function loadRecentMemories(workspace, days = 2) {
869
+ const memoryDir = join(workspace, 'memory');
870
+ const memories = [];
871
+ const today = new Date();
872
+ for (let i = 0; i < days; i++) {
873
+ const date = new Date(today);
874
+ date.setDate(date.getDate() - i);
875
+ const filename = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}.md`;
876
+ const filepath = join(memoryDir, filename);
877
+ try {
878
+ const content = await readFile(filepath, 'utf-8');
879
+ if (content.trim()) {
880
+ memories.push(`# === memory/${filename} ===\n\n${content.trim()}`);
881
+ }
882
+ }
883
+ catch {
884
+ // File doesn't exist, skip
885
+ }
886
+ }
887
+ return memories;
888
+ }
837
889
  export async function bootSequence(workspace, includeMemory = true) {
838
890
  const ws = workspace ??
839
891
  process.env.ASSOAI_WORKSPACE ??
840
892
  join(homedir(), '.openclaw', 'workspace');
893
+ const startTime = Date.now();
841
894
  const files = includeMemory ? BOOT_FILES : BOOT_FILES.filter(f => f !== 'MEMORY.md');
842
- const sections = [];
895
+ const fileMap = new Map();
843
896
  const loaded = [];
844
897
  const missing = [];
898
+ const warnings = [];
899
+ // Load all boot files
845
900
  for (const filename of files) {
846
901
  const filepath = join(ws, filename);
847
- try {
848
- const content = await readFile(filepath, 'utf-8');
849
- if (content.trim()) {
850
- sections.push(`# === ${filename} ===\n\n${content.trim()}`);
851
- loaded.push(filename);
852
- }
853
- else {
854
- missing.push(filename);
855
- }
902
+ const info = await loadBootFile(filepath);
903
+ if (info) {
904
+ fileMap.set(filename, info);
905
+ loaded.push(filename);
856
906
  }
857
- catch {
907
+ else {
858
908
  missing.push(filename);
909
+ // Critical file missing? That's a serious error
910
+ if (CRITICAL_FILES.includes(filename)) {
911
+ warnings.push(`⚠️ CRITICAL: ${filename} 누락 — 정체성/운영 규칙 없이 부팅됨`);
912
+ }
859
913
  }
860
914
  }
861
- if (loaded.length === 0) {
915
+ // Boot sequence failed if no critical files
916
+ const hasCriticalFiles = CRITICAL_FILES.every(f => fileMap.has(f));
917
+ if (!hasCriticalFiles) {
862
918
  return {
863
919
  success: false,
864
- error: `부팅 실패: ${ws} 에서 페르소나 파일을 찾을 없습니다.`,
920
+ error: `부팅 실패: 필수 파일(${CRITICAL_FILES.join(', ')}) 하나 이상 누락. 워크스페이스: ${ws}`,
865
921
  workspace: ws,
922
+ loaded,
866
923
  missing,
924
+ warnings,
925
+ critical_missing: CRITICAL_FILES.filter(f => !fileMap.has(f)),
867
926
  };
868
927
  }
928
+ // Build persona sections
929
+ const sections = [];
930
+ const manifest = {};
931
+ for (const filename of files) {
932
+ const info = fileMap.get(filename);
933
+ if (info) {
934
+ sections.push(`# === ${filename} ===\n\n${info.content}`);
935
+ manifest[filename] = {
936
+ size: info.size,
937
+ hash: info.hash,
938
+ mtime: info.mtime.toISOString(),
939
+ };
940
+ }
941
+ }
942
+ // Load recent daily memories (today + yesterday)
943
+ const recentMemories = includeMemory ? await loadRecentMemories(ws, 2) : [];
944
+ if (recentMemories.length > 0) {
945
+ sections.push(...recentMemories);
946
+ }
947
+ // Build integrity summary
948
+ const soul = fileMap.get('SOUL.md');
949
+ const agents = fileMap.get('AGENTS.md');
950
+ const identity = soul ? `SOUL (${soul.hash.slice(0, 8)})` : 'UNKNOWN';
951
+ const rules = agents ? `AGENTS (${agents.hash.slice(0, 8)})` : 'UNKNOWN';
952
+ const bootTime = Date.now() - startTime;
869
953
  return {
870
954
  success: true,
871
955
  workspace: ws,
872
956
  loaded,
873
957
  missing,
958
+ warnings,
959
+ identity, // 정체성 핵심 해시
960
+ rules, // 운영 규칙 해시
961
+ manifest, // 파일 무결성 정보
962
+ boot_time_ms: bootTime,
963
+ recent_memories: recentMemories.length,
874
964
  persona: sections.join('\n\n---\n\n'),
965
+ summary: `✅ 부팅 완료 (${bootTime}ms) | 정체성: ${identity} | 규칙: ${rules} | 로드: ${loaded.length}/${files.length} | 최근 기억: ${recentMemories.length}건`,
875
966
  };
876
967
  }
877
968
  // ------------------------------------------------------------------
969
+ // 에이전트 고도화 기능 (Agent Excellence)
970
+ // ------------------------------------------------------------------
971
+ export async function agentStatus(orgId, includeMetrics = false) {
972
+ const sb = getSupabase();
973
+ // 기본 상태 정보
974
+ const status = {
975
+ timestamp: new Date().toISOString(),
976
+ uptime_seconds: process.uptime(),
977
+ memory_usage: process.memoryUsage(),
978
+ node_version: process.version,
979
+ platform: process.platform,
980
+ };
981
+ // 조직별 통계
982
+ if (orgId) {
983
+ // 최근 활동 시간 (이벤트 기준)
984
+ const { data: recentEvent } = await sb
985
+ .from('events')
986
+ .select('updated_at')
987
+ .eq('org_id', orgId)
988
+ .order('updated_at', { ascending: false })
989
+ .limit(1)
990
+ .single();
991
+ // 멤버 수
992
+ const { count: memberCount } = await sb
993
+ .from('org_memberships')
994
+ .select('*', { count: 'exact', head: true })
995
+ .eq('org_id', orgId)
996
+ .eq('status', 'APPROVED');
997
+ // 이번 달 이벤트 수
998
+ const startOfMonth = new Date();
999
+ startOfMonth.setDate(1);
1000
+ startOfMonth.setHours(0, 0, 0, 0);
1001
+ const { count: eventCount } = await sb
1002
+ .from('events')
1003
+ .select('*', { count: 'exact', head: true })
1004
+ .eq('org_id', orgId)
1005
+ .gte('created_at', startOfMonth.toISOString());
1006
+ status.organization = {
1007
+ org_id: orgId,
1008
+ last_activity: recentEvent?.updated_at ?? null,
1009
+ member_count: memberCount ?? 0,
1010
+ events_this_month: eventCount ?? 0,
1011
+ };
1012
+ }
1013
+ // 상세 메트릭 (선택)
1014
+ if (includeMetrics) {
1015
+ const { data: totalOrgs } = await sb
1016
+ .from('organizations')
1017
+ .select('id', { count: 'exact', head: true });
1018
+ const { data: totalEvents } = await sb
1019
+ .from('events')
1020
+ .select('id', { count: 'exact', head: true });
1021
+ status.metrics = {
1022
+ total_organizations: totalOrgs?.length ?? 0,
1023
+ total_events: totalEvents?.length ?? 0,
1024
+ };
1025
+ }
1026
+ return status;
1027
+ }
1028
+ export async function knowledgeSearch(orgId, query, contentTypes = ['all'], limit = 10, minRelevance = 0.6) {
1029
+ const sb = getSupabase();
1030
+ const maxResults = Math.min(limit, 50);
1031
+ const results = [];
1032
+ // 검색 대상 결정
1033
+ const searchAll = contentTypes.includes('all');
1034
+ const searchFiles = searchAll || contentTypes.includes('files');
1035
+ const searchEvents = searchAll || contentTypes.includes('events');
1036
+ const searchMinutes = searchAll || contentTypes.includes('minutes');
1037
+ // 파일 검색 (기본 텍스트 매칭)
1038
+ if (searchFiles) {
1039
+ const { data: files } = await sb
1040
+ .from('files')
1041
+ .select('id, original_name, tags, created_at, size')
1042
+ .eq('org_id', orgId)
1043
+ .ilike('original_name', `%${query}%`)
1044
+ .limit(maxResults);
1045
+ if (files) {
1046
+ files.forEach(f => {
1047
+ results.push({
1048
+ type: 'file',
1049
+ id: f.id,
1050
+ title: f.original_name,
1051
+ relevance: 0.8, // 파일명 매칭 기본 점수
1052
+ created_at: f.created_at,
1053
+ metadata: { size: f.size, tags: f.tags },
1054
+ });
1055
+ });
1056
+ }
1057
+ }
1058
+ // 이벤트 검색
1059
+ if (searchEvents) {
1060
+ const { data: events } = await sb
1061
+ .from('events')
1062
+ .select('id, name, description, start_date, category')
1063
+ .eq('org_id', orgId)
1064
+ .or(`name.ilike.%${query}%,description.ilike.%${query}%`)
1065
+ .limit(maxResults);
1066
+ if (events) {
1067
+ events.forEach(e => {
1068
+ const titleMatch = e.name.toLowerCase().includes(query.toLowerCase());
1069
+ results.push({
1070
+ type: 'event',
1071
+ id: e.id,
1072
+ title: e.name,
1073
+ snippet: e.description?.slice(0, 150),
1074
+ relevance: titleMatch ? 0.9 : 0.7,
1075
+ created_at: e.start_date,
1076
+ metadata: { category: e.category },
1077
+ });
1078
+ });
1079
+ }
1080
+ }
1081
+ // 관련도 필터링 및 정렬
1082
+ const filtered = results
1083
+ .filter(r => r.relevance >= minRelevance)
1084
+ .sort((a, b) => b.relevance - a.relevance)
1085
+ .slice(0, maxResults);
1086
+ return {
1087
+ query,
1088
+ results: filtered,
1089
+ total: filtered.length,
1090
+ content_types: contentTypes,
1091
+ };
1092
+ }
1093
+ export async function taskDelegate(orgId, taskDescription, taskType, priority = 'normal', deadline, context, callbackWebhook) {
1094
+ const sb = getSupabase();
1095
+ // 작업 레코드 생성 (tasks 테이블이 있다고 가정, 없으면 로컬 저장소 사용)
1096
+ const taskData = {
1097
+ org_id: orgId,
1098
+ description: taskDescription,
1099
+ task_type: taskType,
1100
+ priority,
1101
+ status: 'PENDING',
1102
+ deadline: deadline ? new Date(deadline).toISOString() : null,
1103
+ context: context ?? {},
1104
+ callback_webhook: callbackWebhook,
1105
+ created_at: new Date().toISOString(),
1106
+ };
1107
+ try {
1108
+ // tasks 테이블이 존재한다면 DB에 저장
1109
+ const { data, error } = await sb
1110
+ .from('tasks')
1111
+ .insert(taskData)
1112
+ .select()
1113
+ .single();
1114
+ if (error) {
1115
+ // 테이블이 없으면 fallback: 메모리에서 시뮬레이션
1116
+ return {
1117
+ ...taskData,
1118
+ success: true,
1119
+ task_id: `task_${Date.now()}`,
1120
+ status: 'DELEGATED',
1121
+ message: '서브에이전트에게 작업이 위임되었습니다 (시뮬레이션 모드)',
1122
+ };
1123
+ }
1124
+ return {
1125
+ ...data,
1126
+ success: true,
1127
+ task_id: data.id,
1128
+ status: 'DELEGATED',
1129
+ message: '서브에이전트에게 작업이 위임되었습니다',
1130
+ };
1131
+ }
1132
+ catch (err) {
1133
+ // 테이블 없음 fallback
1134
+ return {
1135
+ ...taskData,
1136
+ success: true,
1137
+ task_id: `task_${Date.now()}`,
1138
+ status: 'DELEGATED',
1139
+ message: '서브에이전트에게 작업이 위임되었습니다 (시뮬레이션 모드)',
1140
+ };
1141
+ }
1142
+ }
1143
+ export async function growthReport(orgId, periodDays = 30, includeRecommendations = true, format = 'markdown') {
1144
+ const sb = getSupabase();
1145
+ // 분석 기간 계산
1146
+ const startDate = new Date();
1147
+ startDate.setDate(startDate.getDate() - periodDays);
1148
+ // 데이터 수집
1149
+ const [orgData, eventsData, membersData, filesData] = await Promise.all([
1150
+ sb.from('organizations').select('*').eq('id', orgId).single(),
1151
+ sb.from('events').select('*').eq('org_id', orgId).gte('created_at', startDate.toISOString()),
1152
+ sb.from('org_memberships').select('*').eq('org_id', orgId).eq('status', 'APPROVED'),
1153
+ sb.from('files').select('*').eq('org_id', orgId).gte('created_at', startDate.toISOString()),
1154
+ ]);
1155
+ const org = orgData.data;
1156
+ const events = eventsData.data ?? [];
1157
+ const members = membersData.data ?? [];
1158
+ const files = filesData.data ?? [];
1159
+ // 통계 계산
1160
+ const stats = {
1161
+ period_days: periodDays,
1162
+ events_created: events.length,
1163
+ events_completed: events.filter(e => e.status === 'COMPLETED').length,
1164
+ files_uploaded: files.length,
1165
+ total_members: members.length,
1166
+ new_members: members.filter(m => new Date(m.created_at) >= startDate).length,
1167
+ };
1168
+ // 추천 생성
1169
+ const recommendations = includeRecommendations ? [
1170
+ stats.events_created === 0 ? '📅 이번 달 행사가 없습니다. 새 행사를 계획해보세요!' : null,
1171
+ stats.new_members === 0 ? '👥 신규 멤버 유입이 없습니다. 홍보를 강화해보세요!' : null,
1172
+ stats.files_uploaded < 5 ? '📁 문서 관리를 활성화하세요. 회의록과 자료를 업로드해보세요!' : null,
1173
+ ].filter(Boolean) : [];
1174
+ const report = {
1175
+ organization: org?.name ?? 'Unknown',
1176
+ org_id: orgId,
1177
+ report_date: new Date().toISOString(),
1178
+ period: `${periodDays}일`,
1179
+ statistics: stats,
1180
+ recommendations: recommendations.length > 0 ? recommendations : ['✅ 모든 지표가 양호합니다!'],
1181
+ insights: [
1182
+ `이벤트 완료율: ${stats.events_created > 0 ? Math.round((stats.events_completed / stats.events_created) * 100) : 0}%`,
1183
+ `멤버 증가율: ${stats.total_members > 0 ? Math.round((stats.new_members / stats.total_members) * 100) : 0}%`,
1184
+ `평균 파일 업로드: ${Math.round(files.length / periodDays * 7)}/주`,
1185
+ ],
1186
+ };
1187
+ // 포맷 변환
1188
+ if (format === 'markdown') {
1189
+ const md = `# 📊 에이전트 성장 리포트
1190
+
1191
+ **조직:** ${report.organization}
1192
+ **기간:** ${report.period}
1193
+ **생성일:** ${new Date().toLocaleDateString('ko-KR')}
1194
+
1195
+ ## 통계
1196
+
1197
+ - 🎯 생성된 이벤트: ${stats.events_created}개
1198
+ - ✅ 완료된 이벤트: ${stats.events_completed}개
1199
+ - 📁 업로드된 파일: ${stats.files_uploaded}개
1200
+ - 👥 전체 멤버: ${stats.total_members}명
1201
+ - ➕ 신규 멤버: ${stats.new_members}명
1202
+
1203
+ ## 인사이트
1204
+
1205
+ ${report.insights.map(i => `- ${i}`).join('\n')}
1206
+
1207
+ ## 추천 액션
1208
+
1209
+ ${report.recommendations.map(r => `- ${r}`).join('\n')}
1210
+ `;
1211
+ return { format: 'markdown', content: md };
1212
+ }
1213
+ return report;
1214
+ }
1215
+ // ------------------------------------------------------------------
878
1216
  // Dispatcher
879
1217
  // ------------------------------------------------------------------
880
1218
  export async function dispatch(toolName, args, userId) {
@@ -973,6 +1311,15 @@ export async function dispatch(toolName, args, userId) {
973
1311
  // Boot Sequence
974
1312
  case 'boot_sequence':
975
1313
  return bootSequence(args.workspace, args.include_memory ?? true);
1314
+ // Agent Excellence
1315
+ case 'agent_status':
1316
+ return agentStatus(args.org_id, args.include_metrics ?? false);
1317
+ case 'knowledge_search':
1318
+ return knowledgeSearch(args.org_id, args.query, args.content_types ?? ['all'], args.limit ?? 10, args.min_relevance ?? 0.6);
1319
+ case 'task_delegate':
1320
+ return taskDelegate(args.org_id, args.task_description, args.task_type, args.priority ?? 'normal', args.deadline, args.context, args.callback_webhook);
1321
+ case 'growth_report':
1322
+ return growthReport(args.org_id, args.period_days ?? 30, args.include_recommendations ?? true, args.format ?? 'markdown');
976
1323
  default:
977
1324
  throw new Error(`Unknown tool: ${toolName}`);
978
1325
  }
package/dist/tools.d.ts CHANGED
@@ -1156,7 +1156,7 @@ export declare const TOOLS: readonly [{
1156
1156
  };
1157
1157
  }, {
1158
1158
  readonly name: "boot_sequence";
1159
- readonly description: "무펭이 프로토콜 부팅 시퀀스 — 워크스페이스의 SOUL.md → SELF.md → USER.md → MEMORY.md를 순서대로 읽어 에이전트 페르소나 컨텍스트를 구성합니다. 세션 시작 시 가장 먼저 호출하세요.";
1159
+ readonly description: string;
1160
1160
  readonly inputSchema: {
1161
1161
  readonly type: "object";
1162
1162
  readonly properties: {
@@ -1171,4 +1171,115 @@ export declare const TOOLS: readonly [{
1171
1171
  };
1172
1172
  readonly required: string[];
1173
1173
  };
1174
+ }, {
1175
+ readonly name: "agent_status";
1176
+ readonly description: "에이전트의 현재 상태를 요약합니다 — 메모리 사용량, 마지막 활동 시간, 활성 세션 수, 에이전트 레벨 등";
1177
+ readonly inputSchema: {
1178
+ readonly type: "object";
1179
+ readonly properties: {
1180
+ readonly org_id: {
1181
+ readonly type: "string";
1182
+ readonly description: "조직 ID (선택, 없으면 전체 상태)";
1183
+ };
1184
+ readonly include_metrics: {
1185
+ readonly type: "boolean";
1186
+ readonly description: "상세 메트릭 포함 여부 (기본값: false)";
1187
+ };
1188
+ };
1189
+ readonly required: string[];
1190
+ };
1191
+ }, {
1192
+ readonly name: "knowledge_search";
1193
+ readonly description: "조직 지식베이스에서 시맨틱 검색을 수행합니다 — 문서, 회의록, 인수인계 자료 등에서 관련 정보를 찾습니다";
1194
+ readonly inputSchema: {
1195
+ readonly type: "object";
1196
+ readonly properties: {
1197
+ readonly org_id: {
1198
+ readonly type: "string";
1199
+ readonly description: "조직 ID";
1200
+ };
1201
+ readonly query: {
1202
+ readonly type: "string";
1203
+ readonly description: "검색 쿼리 (자연어)";
1204
+ };
1205
+ readonly content_types: {
1206
+ readonly type: "array";
1207
+ readonly items: {
1208
+ readonly type: "string";
1209
+ };
1210
+ readonly description: "검색할 콘텐츠 타입 (files, events, minutes, handovers, all). 기본값: all";
1211
+ };
1212
+ readonly limit: {
1213
+ readonly type: "number";
1214
+ readonly description: "결과 개수 (기본값: 10, 최대: 50)";
1215
+ };
1216
+ readonly min_relevance: {
1217
+ readonly type: "number";
1218
+ readonly description: "최소 관련도 점수 (0.0-1.0, 기본값: 0.6)";
1219
+ };
1220
+ };
1221
+ readonly required: readonly ["org_id", "query"];
1222
+ };
1223
+ }, {
1224
+ readonly name: "task_delegate";
1225
+ readonly description: "서브에이전트에게 작업을 위임합니다 — 복잡한 작업을 독립적인 에이전트 세션으로 분리하여 실행합니다";
1226
+ readonly inputSchema: {
1227
+ readonly type: "object";
1228
+ readonly properties: {
1229
+ readonly org_id: {
1230
+ readonly type: "string";
1231
+ readonly description: "조직 ID";
1232
+ };
1233
+ readonly task_description: {
1234
+ readonly type: "string";
1235
+ readonly description: "작업 설명 (상세할수록 좋음)";
1236
+ };
1237
+ readonly task_type: {
1238
+ readonly type: "string";
1239
+ readonly description: "작업 유형 (research, generation, analysis, automation, other)";
1240
+ };
1241
+ readonly priority: {
1242
+ readonly type: "string";
1243
+ readonly description: "우선순위 (low, normal, high, urgent). 기본값: normal";
1244
+ };
1245
+ readonly deadline: {
1246
+ readonly type: "string";
1247
+ readonly description: "마감 시간 (ISO 8601 형식, 선택)";
1248
+ };
1249
+ readonly context: {
1250
+ readonly type: "object";
1251
+ readonly description: "작업 실행에 필요한 컨텍스트 데이터 (JSON)";
1252
+ };
1253
+ readonly callback_webhook: {
1254
+ readonly type: "string";
1255
+ readonly description: "작업 완료 시 콜백 URL (선택)";
1256
+ };
1257
+ };
1258
+ readonly required: readonly ["org_id", "task_description", "task_type"];
1259
+ };
1260
+ }, {
1261
+ readonly name: "growth_report";
1262
+ readonly description: "에이전트 성장 리포트를 생성합니다 — 활동 통계, 학습 내용, 개선 사항, 추천 액션 등을 포함";
1263
+ readonly inputSchema: {
1264
+ readonly type: "object";
1265
+ readonly properties: {
1266
+ readonly org_id: {
1267
+ readonly type: "string";
1268
+ readonly description: "조직 ID";
1269
+ };
1270
+ readonly period_days: {
1271
+ readonly type: "number";
1272
+ readonly description: "분석 기간 (일 단위, 기본값: 30)";
1273
+ };
1274
+ readonly include_recommendations: {
1275
+ readonly type: "boolean";
1276
+ readonly description: "개선 추천 포함 여부 (기본값: true)";
1277
+ };
1278
+ readonly format: {
1279
+ readonly type: "string";
1280
+ readonly description: "출력 형식 (markdown, json). 기본값: markdown";
1281
+ };
1282
+ };
1283
+ readonly required: readonly ["org_id"];
1284
+ };
1174
1285
  }];
package/dist/tools.js CHANGED
@@ -652,7 +652,17 @@ export const TOOLS = [
652
652
  // ------------------------------------------------------------------
653
653
  {
654
654
  name: 'boot_sequence',
655
- description: '무펭이 프로토콜 부팅 시퀀스 — 워크스페이스의 SOUL.md SELF.md USER.md MEMORY.md를 순서대로 읽어 에이전트 페르소나 컨텍스트를 구성합니다. 세션 시작 시 가장 먼저 호출하세요.',
655
+ description: '무펭이즘 Layer 0 부팅 시퀀스 — 워크스페이스의 핵심 정체성 파일들을 로드하고 무결성을 검증합니다.\n\n' +
656
+ '로드 순서:\n' +
657
+ '1. SOUL.md (정체성 핵심) — 필수\n' +
658
+ '2. AGENTS.md (운영 규칙) — 필수\n' +
659
+ '3. SELF.md (자기 설명)\n' +
660
+ '4. USER.md (사용자 정보)\n' +
661
+ '5. RELATIONS.md (관계 그래프)\n' +
662
+ '6. MEMORY.md (장기 기억)\n' +
663
+ '7. memory/YYYY-MM-DD.md (최근 2일 일일 기억)\n\n' +
664
+ '각 파일의 해시(SHA-256)를 계산하여 무결성을 추적합니다. 필수 파일(SOUL, AGENTS)이 없으면 부팅 실패.\n' +
665
+ '세션 시작 시 가장 먼저 호출하여 조직 에이전트의 정체성과 맥락을 복원하세요.',
656
666
  inputSchema: {
657
667
  type: 'object',
658
668
  properties: {
@@ -668,4 +678,65 @@ export const TOOLS = [
668
678
  required: [],
669
679
  },
670
680
  },
681
+ // ------------------------------------------------------------------
682
+ // 에이전트 고도화 기능 (Agent Excellence)
683
+ // ------------------------------------------------------------------
684
+ {
685
+ name: 'agent_status',
686
+ description: '에이전트의 현재 상태를 요약합니다 — 메모리 사용량, 마지막 활동 시간, 활성 세션 수, 에이전트 레벨 등',
687
+ inputSchema: {
688
+ type: 'object',
689
+ properties: {
690
+ org_id: { type: 'string', description: '조직 ID (선택, 없으면 전체 상태)' },
691
+ include_metrics: { type: 'boolean', description: '상세 메트릭 포함 여부 (기본값: false)' },
692
+ },
693
+ required: [],
694
+ },
695
+ },
696
+ {
697
+ name: 'knowledge_search',
698
+ description: '조직 지식베이스에서 시맨틱 검색을 수행합니다 — 문서, 회의록, 인수인계 자료 등에서 관련 정보를 찾습니다',
699
+ inputSchema: {
700
+ type: 'object',
701
+ properties: {
702
+ org_id: { type: 'string', description: '조직 ID' },
703
+ query: { type: 'string', description: '검색 쿼리 (자연어)' },
704
+ content_types: { type: 'array', items: { type: 'string' }, description: '검색할 콘텐츠 타입 (files, events, minutes, handovers, all). 기본값: all' },
705
+ limit: { type: 'number', description: '결과 개수 (기본값: 10, 최대: 50)' },
706
+ min_relevance: { type: 'number', description: '최소 관련도 점수 (0.0-1.0, 기본값: 0.6)' },
707
+ },
708
+ required: ['org_id', 'query'],
709
+ },
710
+ },
711
+ {
712
+ name: 'task_delegate',
713
+ description: '서브에이전트에게 작업을 위임합니다 — 복잡한 작업을 독립적인 에이전트 세션으로 분리하여 실행합니다',
714
+ inputSchema: {
715
+ type: 'object',
716
+ properties: {
717
+ org_id: { type: 'string', description: '조직 ID' },
718
+ task_description: { type: 'string', description: '작업 설명 (상세할수록 좋음)' },
719
+ task_type: { type: 'string', description: '작업 유형 (research, generation, analysis, automation, other)' },
720
+ priority: { type: 'string', description: '우선순위 (low, normal, high, urgent). 기본값: normal' },
721
+ deadline: { type: 'string', description: '마감 시간 (ISO 8601 형식, 선택)' },
722
+ context: { type: 'object', description: '작업 실행에 필요한 컨텍스트 데이터 (JSON)' },
723
+ callback_webhook: { type: 'string', description: '작업 완료 시 콜백 URL (선택)' },
724
+ },
725
+ required: ['org_id', 'task_description', 'task_type'],
726
+ },
727
+ },
728
+ {
729
+ name: 'growth_report',
730
+ description: '에이전트 성장 리포트를 생성합니다 — 활동 통계, 학습 내용, 개선 사항, 추천 액션 등을 포함',
731
+ inputSchema: {
732
+ type: 'object',
733
+ properties: {
734
+ org_id: { type: 'string', description: '조직 ID' },
735
+ period_days: { type: 'number', description: '분석 기간 (일 단위, 기본값: 30)' },
736
+ include_recommendations: { type: 'boolean', description: '개선 추천 포함 여부 (기본값: true)' },
737
+ format: { type: 'string', description: '출력 형식 (markdown, json). 기본값: markdown' },
738
+ },
739
+ required: ['org_id'],
740
+ },
741
+ },
671
742
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assoai-mcp-server",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "AssoAI MCP Server — 학생자치 조직 데이터 접근을 위한 Model Context Protocol 서버",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",