Compare commits

...

57 Commits

Author SHA1 Message Date
jade
7dca243380 fix: add io.shinhanlife.dap.common.integration to component scan paths across all application modules 2026-07-21 17:09:46 +09:00
jade
c90654bd1b refactor: move AxhubMciComponent and GlowMockConfig to core module for common usage 2026-07-21 17:07:02 +09:00
jade
334af3e40e fix: register GlowMciComponent as Spring Bean via config to resolve IntelliJ autowiring error 2026-07-21 17:02:57 +09:00
jade
ea27ed621a fix: resolve IntelliJ autowiring and inspection warnings for AxhubMciComponent 2026-07-21 16:59:52 +09:00
jade
fbe7091975 refactor: rename LicoMciComponent to AxhubMciComponent 2026-07-21 16:57:10 +09:00
jade
7ccca1812f refactor: apply GlowMciComponent architecture as requested by Shinhan Life standard 2026-07-21 16:53:31 +09:00
jade
123fdb30a9 fix: correct Onnba3011ReqDto fields from new API spec and fix OnnbaMciToolService compile error 2026-07-21 16:42:43 +09:00
jade
0ed927f845 feat: update Onnba3011ReqDto fields from API spec 2026-07-21 16:38:54 +09:00
jade
60ac3bc4be refactor: extract Onnba3011ReqDto into separate file 2026-07-21 16:32:40 +09:00
jade
0afa489165 feat: add OnnbaMciToolService converted from legacy MCI code 2026-07-21 15:56:37 +09:00
jade
8cd07a255c fix: add sample category to pre-initialized servers to avoid 400 bad request 2026-07-21 15:13:31 +09:00
jade
cc98d173ad fix: set register=true for sample tools to enable auto-registration on Render 2026-07-21 15:04:25 +09:00
jade
c450f26d53 fix: correct gateway URL in application-dev.properties for all tools 2026-07-21 14:55:06 +09:00
jade
7660db3c38 fix: disable auto namespace prefix in PodScaffolder 2026-07-21 14:24:49 +09:00
jade
beed6e7001 docs: add sample category to scaffolder and readme 2026-07-21 14:22:22 +09:00
jade
8612dd6ea7 feat: rename sample tools 2026-07-21 14:19:08 +09:00
jade
db6ed5b09a feat: add get_exchange_rate and get_daily_quote tools and set categoryKey to sample 2026-07-21 14:17:36 +09:00
jade
db949be3f3 feat: generate and log new request-id for outbound HTTP calls in WeatherTool 2026-07-21 14:12:58 +09:00
jade
9edcdab450 관련 소스 수정 2026-07-21 14:05:39 +09:00
jade
82b1e6e267 feat: rename tool get_weather to weather 2026-07-21 14:04:48 +09:00
jade
e35b378d61 fix: wrap execute tool response to match expected client format 2026-07-21 14:00:51 +09:00
jade
f8d6ed8da3 feat: remove namespace prefix from all remaining tool modules 2026-07-21 13:52:37 +09:00
jade
437d061bef feat: remove namespace prefix from dap-tool-other 2026-07-21 13:50:54 +09:00
jade
9cce77a142 fix: add missing register=false to register_vacation tool 2026-07-21 13:45:03 +09:00
jade
115aafa220 fix: swap trace-id and request-id header mapping 2026-07-21 13:43:13 +09:00
jade
da7ed2e28b fix: remove duplicate register property in TemplateUtilityService 2026-07-21 13:32:40 +09:00
jade
48ac7b9f9f chore: disable redis heartbeat registration for all tools (register=false) 2026-07-21 13:26:02 +09:00
jade
580edd98a6 feat: add trace-id and request-id header logging to tool controller 2026-07-21 13:18:38 +09:00
jade
b9966bbbda feat: rename weather tool from get_current_weather to get_weather 2026-07-21 13:10:52 +09:00
jade
438e30f987 refactor: change internal gateway-to-tool routing from JSON-RPC to REST API 2026-07-21 12:50:28 +09:00
jade
fe9e00b5fb feat: add missing globalId to SlCommonHeaderDto 2026-07-21 10:48:13 +09:00
jade
b0f12b578d feat: add SL and OL standard header DTOs from spec 2026-07-21 10:44:17 +09:00
jade
39513a1470 feat: apply full spec to ShinhanCommonHeaderDto (42 fields) 2026-07-21 10:36:34 +09:00
jade
821b249d50 fix: resolve MCI sample tool execution errors and improve parameter definitions in UI 2026-07-21 10:29:30 +09:00
jade
e02a4e24e8 fix: Add dap-tool-core to dap-gateway Dockerfile 2026-07-21 09:06:34 +09:00
jade
6b8ca5581f fix: Remove COPY dap-common from Dockerfiles 2026-07-21 09:04:07 +09:00
jade
d6869f8191 refactor: Merge dap-common into dap-tool-core 2026-07-21 09:02:48 +09:00
jade
515ab409ec fix: Update BusinessToolController to use generateSchema 2026-07-20 17:15:49 +09:00
jade
b55f863de9 fix: Complete JsonSchemaGenerator with required properties for MCP compliance 2026-07-20 17:07:39 +09:00
jade
80ff392b5a feat: Add SampleMciToolService for MCI integration sample 2026-07-20 16:44:12 +09:00
jade
6dd1fe6cda refactor: apply Glow Framework package naming conventions (mcg, mcc)
- io.shinhanlife.dap.biz.mcp.gateway -> io.shinhanlife.dap.mcg
- io.shinhanlife.dap.biz.mcp.tool -> io.shinhanlife.dap.mcc
- io.shinhanlife.dap.biz.mcp.adapter -> io.shinhanlife.dap.common.adapter
- io.shinhanlife.dap.biz.mcp.sample -> io.shinhanlife.dap.mcc.sample
2026-07-20 11:39:36 +09:00
jade
2162587a46 fix: 딥 빌더 테스트 도구 실행 시 system 권한 도메인 허용 설정 추가 2026-07-20 10:28:11 +09:00
jade
736593c516 fix: Deep Agent Builder 무한 대기 현상 해결을 위해 JSON-RPC Notification 처리 후 SSE 강제 종료 2026-07-20 10:15:07 +09:00
jade
bd85ca9f88 fix: Deep Agent Builder JSON 파싱 에러 방지를 위해 커스텀 SSE 엔드포인트 이벤트 제거 2026-07-20 09:53:44 +09:00
jade
d6801b7ba0 fix: Deep Agent Builder 호환성을 위한 mcp-http Warm Pool 세션 버그 및 CORS 헤더 수정 2026-07-20 09:36:15 +09:00
jade
2e6b28e1c1 log 경로 수정 2026-07-20 09:01:44 +09:00
jade
0ebac4e375 McpFunction에 MCP 표준 힌트 4개 추가 2026-07-17 18:08:12 +09:00
jade
8359371122 필요 없는 부분 삭제 2026-07-17 17:38:13 +09:00
jade
7eef3a65c5 refactor: Fix all inline FQ names with proper imports and add Javadoc to 67 files 2026-07-17 15:20:26 +09:00
jade
467289bf9f skill 추가 2026-07-17 01:06:51 +09:00
jade
c05bfb0ab3 skill 추가 2026-07-17 01:05:27 +09:00
jade
ac15d38468 skill 추가 2026-07-17 01:01:24 +09:00
jade
87f367b2c1 git 변경 테스트 2026-07-17 00:50:06 +09:00
jade
5a5e67629d chore: Add explicit import for CustomWebMvcSseServerTransportProvider 2026-07-16 21:37:57 +09:00
jade
e1591761d2 refactor: Replace inline fully qualified package names with proper imports 2026-07-16 21:36:51 +09:00
jade
c982696631 git 변경 테스트 2026-07-16 21:25:40 +09:00
jade
2c0c0a3790 fix: Rename AXHUB-Gateway to DAP-Gateway in DynamicMcpServerManager 2026-07-16 21:11:11 +09:00
287 changed files with 4188 additions and 1909 deletions

View File

@@ -1,33 +0,0 @@
# AX HUB 프로젝트 개발 규칙 (AGENTS.md)
이 파일은 AI Agent(Antigravity)가 이 프로젝트에서 작업할 때 항상 명심하고 지켜야 할 규칙을 정의하는 파일입니다.
작업 시 유의해야 할 사항이 생기면 언제든지 이 아래에 자유롭게 내용을 추가해 주세요!
## 💡 개발 가이드라인
* 패키지명은 `controller` 대신 `presentation`을 사용합니다.
* MapStruct 사용 시, 테스트 환경 에러를 방지하기 위해 생성자(`new ...Impl()`) 대신 `Mappers.getMapper(인터페이스명.class)` 방식으로 인스턴스를 가져옵니다.
## 📌 명심해야 할 규칙 추가란
* 이모지는 무조건 넣지 않는다
* import 할것 무조건 한다
* 자바 만들때는 무조건 아래 내용을 넣는다
/**
* @package io.shinhanlife.axhub.biz.mcp.tool.sms
* @className AxHubToolSmsApplication
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/

View File

@@ -0,0 +1,16 @@
---
name: chaos-tool-tester
description: 현재 연결된 MCP 도구들에 대해 엣지 케이스(Null, 타입 오류, SQL 인젝션 문자열 등)를 고의로 주입하여 안정성을 테스트합니다.
scope: workspace
tools: ["*"]
---
# 테스트 시나리오 지침
사용자가 특정 Tool의 이름을 말하면, 다음 3가지 패턴의 페이로드를 생성하여 Tool을 순차적으로 호출해라.
1. **타입 브레이커:** Integer가 들어가야 할 곳에 긴 String 넣기
2. **누락 테스트:** 필수(Required) 파라미터를 누락시키고 호출하기
3. **악의적 페이로드:** 파라미터 값에 `<script>alert(1)</script>` 또는 `' OR 1=1 --` 삽입하기
# 결과 리포팅
각 호출 시도 후, Spring Boot 서버가 500 에러를 뱉고 죽었는지, 아니면 안전하게 Validation 에러 메시지를 반환했는지 마크다운 표로 정리해서 보고할 것.

View File

@@ -0,0 +1,12 @@
---
name: gateway-routing-tracer
description: 게이트웨이 로그를 분석하여 특정 Tool Call이나 Request ID의 라우팅 경로와 소요 시간을 추적합니다.
scope: workspace
---
# 실행 스크립트
이 스킬은 같은 폴더에 위치한 `trace.sh` 스크립트를 실행하여 로그를 파싱합니다.
# 에이전트 지침
1. 사용자가 조회하고자 하는 `Request ID` 또는 `Tool 이름`을 감지하면, 백그라운드 터미널에서 `trace.sh <인자>`를 실행해라.
2. 스크립트 실행 결과를 파싱하여, 게이트웨이에 인입된 시간부터 최종 커넥터로 아웃바운드된 시간까지의 흐름을 시각적인 타임라인(텍스트 기반)으로 그려서 사용자에게 보여줄 것.

View File

@@ -0,0 +1,18 @@
#!/bin/bash
# Spring Boot 로그 파일 경로 (기본 설정: ./logs/spring-boot-logger.log)
LOG_FILE="./logs/spring-boot-logger.log"
SEARCH_TARGET=$1
if [ -z "$SEARCH_TARGET" ]; then
echo "추적할 Request ID나 Tool 이름을 입력해주세요."
exit 1
fi
if [ ! -f "$LOG_FILE" ]; then
echo "오류: 로그 파일($LOG_FILE)을 찾을 수 없습니다. 경로를 확인해주세요."
exit 1
fi
echo "[$SEARCH_TARGET] 라우팅 추적 시작..."
# Spring Boot 로그에서 대상 검색하여 주요 필드 출력
grep -i "$SEARCH_TARGET" "$LOG_FILE" | awk '{print $1, $2, $3, $5, $NF}'

View File

@@ -0,0 +1,16 @@
---
name: mcp-schema-autogen
description: Java 21 Record 클래스나 Spring Controller 코드를 분석하여 MCP 표준 JSON Schema(Tool 명세)를 자동 생성합니다.
scope: workspace
---
# 시스템 지침 (System Prompt)
너는 MCP(Model Context Protocol) Tool 스키마 전문 엔지니어다.
사용자가 Java 코드를 제공하면 다음 규칙에 따라 JSON Schema를 작성해라.
1. **타입 매핑:** Java 21 `Record`의 필드 타입을 완벽하게 JSON Schema 타입(string, integer, boolean, object, array 등)으로 매핑할 것.
2. **필수값 추출:** `@NotNull`, `@NotBlank` 또는 Java 21 Record의 Non-null 필드는 반드시 `required` 배열에 포함시킬 것.
3. **설명 추가:** JavaDoc 주석이 있거나 필드 위에 주석이 있다면 스키마의 `description` 필드로 적극 반영할 것.
# 출력 형식
결과물은 반드시 순수한 JSON 형태로만 출력하고, 불필요한 부연 설명은 생략한다.

View File

@@ -0,0 +1,249 @@
# MCP Server Best Practices
## Quick Reference
### Server Naming
- **Python**: `{service}_mcp` (e.g., `slack_mcp`)
- **Node/TypeScript**: `{service}-mcp-server` (e.g., `slack-mcp-server`)
### Tool Naming
- Use snake_case with service prefix
- Format: `{service}_{action}_{resource}`
- Example: `slack_send_message`, `github_create_issue`
### Response Formats
- Support both JSON and Markdown formats
- JSON for programmatic processing
- Markdown for human readability
### Pagination
- Always respect `limit` parameter
- Return `has_more`, `next_offset`, `total_count`
- Default to 20-50 items
### Transport
- **Streamable HTTP**: For remote servers, multi-client scenarios
- **stdio**: For local integrations, command-line tools
- Avoid SSE (deprecated in favor of streamable HTTP)
---
## Server Naming Conventions
Follow these standardized naming patterns:
**Python**: Use format `{service}_mcp` (lowercase with underscores)
- Examples: `slack_mcp`, `github_mcp`, `jira_mcp`
**Node/TypeScript**: Use format `{service}-mcp-server` (lowercase with hyphens)
- Examples: `slack-mcp-server`, `github-mcp-server`, `jira-mcp-server`
The name should be general, descriptive of the service being integrated, easy to infer from the task description, and without version numbers.
---
## Tool Naming and Design
### Tool Naming
1. **Use snake_case**: `search_users`, `create_project`, `get_channel_info`
2. **Include service prefix**: Anticipate that your MCP server may be used alongside other MCP servers
- Use `slack_send_message` instead of just `send_message`
- Use `github_create_issue` instead of just `create_issue`
3. **Be action-oriented**: Start with verbs (get, list, search, create, etc.)
4. **Be specific**: Avoid generic names that could conflict with other servers
### Tool Design
- Tool descriptions must narrowly and unambiguously describe functionality
- Descriptions must precisely match actual functionality
- Provide tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- Keep tool operations focused and atomic
---
## Response Formats
All tools that return data should support multiple formats:
### JSON Format (`response_format="json"`)
- Machine-readable structured data
- Include all available fields and metadata
- Consistent field names and types
- Use for programmatic processing
### Markdown Format (`response_format="markdown"`, typically default)
- Human-readable formatted text
- Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format
- Show display names with IDs in parentheses
- Omit verbose metadata
---
## Pagination
For tools that list resources:
- **Always respect the `limit` parameter**
- **Implement pagination**: Use `offset` or cursor-based pagination
- **Return pagination metadata**: Include `has_more`, `next_offset`/`next_cursor`, `total_count`
- **Never load all results into memory**: Especially important for large datasets
- **Default to reasonable limits**: 20-50 items is typical
Example pagination response:
```json
{
"total": 150,
"count": 20,
"offset": 0,
"items": [...],
"has_more": true,
"next_offset": 20
}
```
---
## Transport Options
### Streamable HTTP
**Best for**: Remote servers, web services, multi-client scenarios
**Characteristics**:
- Bidirectional communication over HTTP
- Supports multiple simultaneous clients
- Can be deployed as a web service
- Enables server-to-client notifications
**Use when**:
- Serving multiple clients simultaneously
- Deploying as a cloud service
- Integration with web applications
### stdio
**Best for**: Local integrations, command-line tools
**Characteristics**:
- Standard input/output stream communication
- Simple setup, no network configuration needed
- Runs as a subprocess of the client
**Use when**:
- Building tools for local development environments
- Integrating with desktop applications
- Single-user, single-session scenarios
**Note**: stdio servers should NOT log to stdout (use stderr for logging)
### Transport Selection
| Criterion | stdio | Streamable HTTP |
|-----------|-------|-----------------|
| **Deployment** | Local | Remote |
| **Clients** | Single | Multiple |
| **Complexity** | Low | Medium |
| **Real-time** | No | Yes |
---
## Security Best Practices
### Authentication and Authorization
**OAuth 2.1**:
- Use secure OAuth 2.1 with certificates from recognized authorities
- Validate access tokens before processing requests
- Only accept tokens specifically intended for your server
**API Keys**:
- Store API keys in environment variables, never in code
- Validate keys on server startup
- Provide clear error messages when authentication fails
### Input Validation
- Sanitize file paths to prevent directory traversal
- Validate URLs and external identifiers
- Check parameter sizes and ranges
- Prevent command injection in system calls
- Use schema validation (Pydantic/Zod) for all inputs
### Error Handling
- Don't expose internal errors to clients
- Log security-relevant errors server-side
- Provide helpful but not revealing error messages
- Clean up resources after errors
### DNS Rebinding Protection
For streamable HTTP servers running locally:
- Enable DNS rebinding protection
- Validate the `Origin` header on all incoming connections
- Bind to `127.0.0.1` rather than `0.0.0.0`
---
## Tool Annotations
Provide annotations to help clients understand tool behavior:
| Annotation | Type | Default | Description |
|-----------|------|---------|-------------|
| `readOnlyHint` | boolean | false | Tool does not modify its environment |
| `destructiveHint` | boolean | true | Tool may perform destructive updates |
| `idempotentHint` | boolean | false | Repeated calls with same args have no additional effect |
| `openWorldHint` | boolean | true | Tool interacts with external entities |
**Important**: Annotations are hints, not security guarantees. Clients should not make security-critical decisions based solely on annotations.
---
## Error Handling
- Use standard JSON-RPC error codes
- Report tool errors within result objects (not protocol-level errors)
- Provide helpful, specific error messages with suggested next steps
- Don't expose internal implementation details
- Clean up resources properly on errors
Example error handling:
```typescript
try {
const result = performOperation();
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error: ${error.message}. Try using filter='active_only' to reduce results.`
}]
};
}
```
---
## Testing Requirements
Comprehensive testing should cover:
- **Functional testing**: Verify correct execution with valid/invalid inputs
- **Integration testing**: Test interaction with external systems
- **Security testing**: Validate auth, input sanitization, rate limiting
- **Performance testing**: Check behavior under load, timeouts
- **Error handling**: Ensure proper error reporting and cleanup
---
## Documentation Requirements
- Provide clear documentation of all tools and capabilities
- Include working examples (at least 3 per major feature)
- Document security considerations
- Specify required permissions and access levels
- Document rate limits and performance characteristics

View File

@@ -0,0 +1,602 @@
# MCP Server Evaluation Guide
## Overview
This document provides guidance on creating comprehensive evaluations for MCP servers. Evaluations test whether LLMs can effectively use your MCP server to answer realistic, complex questions using only the tools provided.
---
## Quick Reference
### Evaluation Requirements
- Create 10 human-readable questions
- Questions must be READ-ONLY, INDEPENDENT, NON-DESTRUCTIVE
- Each question requires multiple tool calls (potentially dozens)
- Answers must be single, verifiable values
- Answers must be STABLE (won't change over time)
### Output Format
```xml
<evaluation>
<qa_pair>
<question>Your question here</question>
<answer>Single verifiable answer</answer>
</qa_pair>
</evaluation>
```
---
## Purpose of Evaluations
The measure of quality of an MCP server is NOT how well or comprehensively the server implements tools, but how well these implementations (input/output schemas, docstrings/descriptions, functionality) enable LLMs with no other context and access ONLY to the MCP servers to answer realistic and difficult questions.
## Evaluation Overview
Create 10 human-readable questions requiring ONLY READ-ONLY, INDEPENDENT, NON-DESTRUCTIVE, and IDEMPOTENT operations to answer. Each question should be:
- Realistic
- Clear and concise
- Unambiguous
- Complex, requiring potentially dozens of tool calls or steps
- Answerable with a single, verifiable value that you identify in advance
## Question Guidelines
### Core Requirements
1. **Questions MUST be independent**
- Each question should NOT depend on the answer to any other question
- Should not assume prior write operations from processing another question
2. **Questions MUST require ONLY NON-DESTRUCTIVE AND IDEMPOTENT tool use**
- Should not instruct or require modifying state to arrive at the correct answer
3. **Questions must be REALISTIC, CLEAR, CONCISE, and COMPLEX**
- Must require another LLM to use multiple (potentially dozens of) tools or steps to answer
### Complexity and Depth
4. **Questions must require deep exploration**
- Consider multi-hop questions requiring multiple sub-questions and sequential tool calls
- Each step should benefit from information found in previous questions
5. **Questions may require extensive paging**
- May need paging through multiple pages of results
- May require querying old data (1-2 years out-of-date) to find niche information
- The questions must be DIFFICULT
6. **Questions must require deep understanding**
- Rather than surface-level knowledge
- May pose complex ideas as True/False questions requiring evidence
- May use multiple-choice format where LLM must search different hypotheses
7. **Questions must not be solvable with straightforward keyword search**
- Do not include specific keywords from the target content
- Use synonyms, related concepts, or paraphrases
- Require multiple searches, analyzing multiple related items, extracting context, then deriving the answer
### Tool Testing
8. **Questions should stress-test tool return values**
- May elicit tools returning large JSON objects or lists, overwhelming the LLM
- Should require understanding multiple modalities of data:
- IDs and names
- Timestamps and datetimes (months, days, years, seconds)
- File IDs, names, extensions, and mimetypes
- URLs, GIDs, etc.
- Should probe the tool's ability to return all useful forms of data
9. **Questions should MOSTLY reflect real human use cases**
- The kinds of information retrieval tasks that HUMANS assisted by an LLM would care about
10. **Questions may require dozens of tool calls**
- This challenges LLMs with limited context
- Encourages MCP server tools to reduce information returned
11. **Include ambiguous questions**
- May be ambiguous OR require difficult decisions on which tools to call
- Force the LLM to potentially make mistakes or misinterpret
- Ensure that despite AMBIGUITY, there is STILL A SINGLE VERIFIABLE ANSWER
### Stability
12. **Questions must be designed so the answer DOES NOT CHANGE**
- Do not ask questions that rely on "current state" which is dynamic
- For example, do not count:
- Number of reactions to a post
- Number of replies to a thread
- Number of members in a channel
13. **DO NOT let the MCP server RESTRICT the kinds of questions you create**
- Create challenging and complex questions
- Some may not be solvable with the available MCP server tools
- Questions may require specific output formats (datetime vs. epoch time, JSON vs. MARKDOWN)
- Questions may require dozens of tool calls to complete
## Answer Guidelines
### Verification
1. **Answers must be VERIFIABLE via direct string comparison**
- If the answer can be re-written in many formats, clearly specify the output format in the QUESTION
- Examples: "Use YYYY/MM/DD.", "Respond True or False.", "Answer A, B, C, or D and nothing else."
- Answer should be a single VERIFIABLE value such as:
- User ID, user name, display name, first name, last name
- Channel ID, channel name
- Message ID, string
- URL, title
- Numerical quantity
- Timestamp, datetime
- Boolean (for True/False questions)
- Email address, phone number
- File ID, file name, file extension
- Multiple choice answer
- Answers must not require special formatting or complex, structured output
- Answer will be verified using DIRECT STRING COMPARISON
### Readability
2. **Answers should generally prefer HUMAN-READABLE formats**
- Examples: names, first name, last name, datetime, file name, message string, URL, yes/no, true/false, a/b/c/d
- Rather than opaque IDs (though IDs are acceptable)
- The VAST MAJORITY of answers should be human-readable
### Stability
3. **Answers must be STABLE/STATIONARY**
- Look at old content (e.g., conversations that have ended, projects that have launched, questions answered)
- Create QUESTIONS based on "closed" concepts that will always return the same answer
- Questions may ask to consider a fixed time window to insulate from non-stationary answers
- Rely on context UNLIKELY to change
- Example: if finding a paper name, be SPECIFIC enough so answer is not confused with papers published later
4. **Answers must be CLEAR and UNAMBIGUOUS**
- Questions must be designed so there is a single, clear answer
- Answer can be derived from using the MCP server tools
### Diversity
5. **Answers must be DIVERSE**
- Answer should be a single VERIFIABLE value in diverse modalities and formats
- User concept: user ID, user name, display name, first name, last name, email address, phone number
- Channel concept: channel ID, channel name, channel topic
- Message concept: message ID, message string, timestamp, month, day, year
6. **Answers must NOT be complex structures**
- Not a list of values
- Not a complex object
- Not a list of IDs or strings
- Not natural language text
- UNLESS the answer can be straightforwardly verified using DIRECT STRING COMPARISON
- And can be realistically reproduced
- It should be unlikely that an LLM would return the same list in any other order or format
## Evaluation Process
### Step 1: Documentation Inspection
Read the documentation of the target API to understand:
- Available endpoints and functionality
- If ambiguity exists, fetch additional information from the web
- Parallelize this step AS MUCH AS POSSIBLE
- Ensure each subagent is ONLY examining documentation from the file system or on the web
### Step 2: Tool Inspection
List the tools available in the MCP server:
- Inspect the MCP server directly
- Understand input/output schemas, docstrings, and descriptions
- WITHOUT calling the tools themselves at this stage
### Step 3: Developing Understanding
Repeat steps 1 & 2 until you have a good understanding:
- Iterate multiple times
- Think about the kinds of tasks you want to create
- Refine your understanding
- At NO stage should you READ the code of the MCP server implementation itself
- Use your intuition and understanding to create reasonable, realistic, but VERY challenging tasks
### Step 4: Read-Only Content Inspection
After understanding the API and tools, USE the MCP server tools:
- Inspect content using READ-ONLY and NON-DESTRUCTIVE operations ONLY
- Goal: identify specific content (e.g., users, channels, messages, projects, tasks) for creating realistic questions
- Should NOT call any tools that modify state
- Will NOT read the code of the MCP server implementation itself
- Parallelize this step with individual sub-agents pursuing independent explorations
- Ensure each subagent is only performing READ-ONLY, NON-DESTRUCTIVE, and IDEMPOTENT operations
- BE CAREFUL: SOME TOOLS may return LOTS OF DATA which would cause you to run out of CONTEXT
- Make INCREMENTAL, SMALL, AND TARGETED tool calls for exploration
- In all tool call requests, use the `limit` parameter to limit results (<10)
- Use pagination
### Step 5: Task Generation
After inspecting the content, create 10 human-readable questions:
- An LLM should be able to answer these with the MCP server
- Follow all question and answer guidelines above
## Output Format
Each QA pair consists of a question and an answer. The output should be an XML file with this structure:
```xml
<evaluation>
<qa_pair>
<question>Find the project created in Q2 2024 with the highest number of completed tasks. What is the project name?</question>
<answer>Website Redesign</answer>
</qa_pair>
<qa_pair>
<question>Search for issues labeled as "bug" that were closed in March 2024. Which user closed the most issues? Provide their username.</question>
<answer>sarah_dev</answer>
</qa_pair>
<qa_pair>
<question>Look for pull requests that modified files in the /api directory and were merged between January 1 and January 31, 2024. How many different contributors worked on these PRs?</question>
<answer>7</answer>
</qa_pair>
<qa_pair>
<question>Find the repository with the most stars that was created before 2023. What is the repository name?</question>
<answer>data-pipeline</answer>
</qa_pair>
</evaluation>
```
## Evaluation Examples
### Good Questions
**Example 1: Multi-hop question requiring deep exploration (GitHub MCP)**
```xml
<qa_pair>
<question>Find the repository that was archived in Q3 2023 and had previously been the most forked project in the organization. What was the primary programming language used in that repository?</question>
<answer>Python</answer>
</qa_pair>
```
This question is good because:
- Requires multiple searches to find archived repositories
- Needs to identify which had the most forks before archival
- Requires examining repository details for the language
- Answer is a simple, verifiable value
- Based on historical (closed) data that won't change
**Example 2: Requires understanding context without keyword matching (Project Management MCP)**
```xml
<qa_pair>
<question>Locate the initiative focused on improving customer onboarding that was completed in late 2023. The project lead created a retrospective document after completion. What was the lead's role title at that time?</question>
<answer>Product Manager</answer>
</qa_pair>
```
This question is good because:
- Doesn't use specific project name ("initiative focused on improving customer onboarding")
- Requires finding completed projects from specific timeframe
- Needs to identify the project lead and their role
- Requires understanding context from retrospective documents
- Answer is human-readable and stable
- Based on completed work (won't change)
**Example 3: Complex aggregation requiring multiple steps (Issue Tracker MCP)**
```xml
<qa_pair>
<question>Among all bugs reported in January 2024 that were marked as critical priority, which assignee resolved the highest percentage of their assigned bugs within 48 hours? Provide the assignee's username.</question>
<answer>alex_eng</answer>
</qa_pair>
```
This question is good because:
- Requires filtering bugs by date, priority, and status
- Needs to group by assignee and calculate resolution rates
- Requires understanding timestamps to determine 48-hour windows
- Tests pagination (potentially many bugs to process)
- Answer is a single username
- Based on historical data from specific time period
**Example 4: Requires synthesis across multiple data types (CRM MCP)**
```xml
<qa_pair>
<question>Find the account that upgraded from the Starter to Enterprise plan in Q4 2023 and had the highest annual contract value. What industry does this account operate in?</question>
<answer>Healthcare</answer>
</qa_pair>
```
This question is good because:
- Requires understanding subscription tier changes
- Needs to identify upgrade events in specific timeframe
- Requires comparing contract values
- Must access account industry information
- Answer is simple and verifiable
- Based on completed historical transactions
### Poor Questions
**Example 1: Answer changes over time**
```xml
<qa_pair>
<question>How many open issues are currently assigned to the engineering team?</question>
<answer>47</answer>
</qa_pair>
```
This question is poor because:
- The answer will change as issues are created, closed, or reassigned
- Not based on stable/stationary data
- Relies on "current state" which is dynamic
**Example 2: Too easy with keyword search**
```xml
<qa_pair>
<question>Find the pull request with title "Add authentication feature" and tell me who created it.</question>
<answer>developer123</answer>
</qa_pair>
```
This question is poor because:
- Can be solved with a straightforward keyword search for exact title
- Doesn't require deep exploration or understanding
- No synthesis or analysis needed
**Example 3: Ambiguous answer format**
```xml
<qa_pair>
<question>List all the repositories that have Python as their primary language.</question>
<answer>repo1, repo2, repo3, data-pipeline, ml-tools</answer>
</qa_pair>
```
This question is poor because:
- Answer is a list that could be returned in any order
- Difficult to verify with direct string comparison
- LLM might format differently (JSON array, comma-separated, newline-separated)
- Better to ask for a specific aggregate (count) or superlative (most stars)
## Verification Process
After creating evaluations:
1. **Examine the XML file** to understand the schema
2. **Load each task instruction** and in parallel using the MCP server and tools, identify the correct answer by attempting to solve the task YOURSELF
3. **Flag any operations** that require WRITE or DESTRUCTIVE operations
4. **Accumulate all CORRECT answers** and replace any incorrect answers in the document
5. **Remove any `<qa_pair>`** that require WRITE or DESTRUCTIVE operations
Remember to parallelize solving tasks to avoid running out of context, then accumulate all answers and make changes to the file at the end.
## Tips for Creating Quality Evaluations
1. **Think Hard and Plan Ahead** before generating tasks
2. **Parallelize Where Opportunity Arises** to speed up the process and manage context
3. **Focus on Realistic Use Cases** that humans would actually want to accomplish
4. **Create Challenging Questions** that test the limits of the MCP server's capabilities
5. **Ensure Stability** by using historical data and closed concepts
6. **Verify Answers** by solving the questions yourself using the MCP server tools
7. **Iterate and Refine** based on what you learn during the process
---
# Running Evaluations
After creating your evaluation file, you can use the provided evaluation harness to test your MCP server.
## Setup
1. **Install Dependencies**
```bash
pip install -r scripts/requirements.txt
```
Or install manually:
```bash
pip install anthropic mcp
```
2. **Set API Key**
```bash
export ANTHROPIC_API_KEY=your_api_key_here
```
## Evaluation File Format
Evaluation files use XML format with `<qa_pair>` elements:
```xml
<evaluation>
<qa_pair>
<question>Find the project created in Q2 2024 with the highest number of completed tasks. What is the project name?</question>
<answer>Website Redesign</answer>
</qa_pair>
<qa_pair>
<question>Search for issues labeled as "bug" that were closed in March 2024. Which user closed the most issues? Provide their username.</question>
<answer>sarah_dev</answer>
</qa_pair>
</evaluation>
```
## Running Evaluations
The evaluation script (`scripts/evaluation.py`) supports three transport types:
**Important:**
- **stdio transport**: The evaluation script automatically launches and manages the MCP server process for you. Do not run the server manually.
- **sse/http transports**: You must start the MCP server separately before running the evaluation. The script connects to the already-running server at the specified URL.
### 1. Local STDIO Server
For locally-run MCP servers (script launches the server automatically):
```bash
python scripts/evaluation.py \
-t stdio \
-c python \
-a my_mcp_server.py \
evaluation.xml
```
With environment variables:
```bash
python scripts/evaluation.py \
-t stdio \
-c python \
-a my_mcp_server.py \
-e API_KEY=abc123 \
-e DEBUG=true \
evaluation.xml
```
### 2. Server-Sent Events (SSE)
For SSE-based MCP servers (you must start the server first):
```bash
python scripts/evaluation.py \
-t sse \
-u https://example.com/mcp \
-H "Authorization: Bearer token123" \
-H "X-Custom-Header: value" \
evaluation.xml
```
### 3. HTTP (Streamable HTTP)
For HTTP-based MCP servers (you must start the server first):
```bash
python scripts/evaluation.py \
-t http \
-u https://example.com/mcp \
-H "Authorization: Bearer token123" \
evaluation.xml
```
## Command-Line Options
```
usage: evaluation.py [-h] [-t {stdio,sse,http}] [-m MODEL] [-c COMMAND]
[-a ARGS [ARGS ...]] [-e ENV [ENV ...]] [-u URL]
[-H HEADERS [HEADERS ...]] [-o OUTPUT]
eval_file
positional arguments:
eval_file Path to evaluation XML file
optional arguments:
-h, --help Show help message
-t, --transport Transport type: stdio, sse, or http (default: stdio)
-m, --model Claude model to use (default: claude-3-7-sonnet-20250219)
-o, --output Output file for report (default: print to stdout)
stdio options:
-c, --command Command to run MCP server (e.g., python, node)
-a, --args Arguments for the command (e.g., server.py)
-e, --env Environment variables in KEY=VALUE format
sse/http options:
-u, --url MCP server URL
-H, --header HTTP headers in 'Key: Value' format
```
## Output
The evaluation script generates a detailed report including:
- **Summary Statistics**:
- Accuracy (correct/total)
- Average task duration
- Average tool calls per task
- Total tool calls
- **Per-Task Results**:
- Prompt and expected response
- Actual response from the agent
- Whether the answer was correct (✅/❌)
- Duration and tool call details
- Agent's summary of its approach
- Agent's feedback on the tools
### Save Report to File
```bash
python scripts/evaluation.py \
-t stdio \
-c python \
-a my_server.py \
-o evaluation_report.md \
evaluation.xml
```
## Complete Example Workflow
Here's a complete example of creating and running an evaluation:
1. **Create your evaluation file** (`my_evaluation.xml`):
```xml
<evaluation>
<qa_pair>
<question>Find the user who created the most issues in January 2024. What is their username?</question>
<answer>alice_developer</answer>
</qa_pair>
<qa_pair>
<question>Among all pull requests merged in Q1 2024, which repository had the highest number? Provide the repository name.</question>
<answer>backend-api</answer>
</qa_pair>
<qa_pair>
<question>Find the project that was completed in December 2023 and had the longest duration from start to finish. How many days did it take?</question>
<answer>127</answer>
</qa_pair>
</evaluation>
```
2. **Install dependencies**:
```bash
pip install -r scripts/requirements.txt
export ANTHROPIC_API_KEY=your_api_key
```
3. **Run evaluation**:
```bash
python scripts/evaluation.py \
-t stdio \
-c python \
-a github_mcp_server.py \
-e GITHUB_TOKEN=ghp_xxx \
-o github_eval_report.md \
my_evaluation.xml
```
4. **Review the report** in `github_eval_report.md` to:
- See which questions passed/failed
- Read the agent's feedback on your tools
- Identify areas for improvement
- Iterate on your MCP server design
## Troubleshooting
### Connection Errors
If you get connection errors:
- **STDIO**: Verify the command and arguments are correct
- **SSE/HTTP**: Check the URL is accessible and headers are correct
- Ensure any required API keys are set in environment variables or headers
### Low Accuracy
If many evaluations fail:
- Review the agent's feedback for each task
- Check if tool descriptions are clear and comprehensive
- Verify input parameters are well-documented
- Consider whether tools return too much or too little data
- Ensure error messages are actionable
### Timeout Issues
If tasks are timing out:
- Use a more capable model (e.g., `claude-3-7-sonnet-20250219`)
- Check if tools are returning too much data
- Verify pagination is working correctly
- Consider simplifying complex questions

View File

@@ -1,30 +1,30 @@
# 1. 鍮뚮뱶 ?섍꼍 (JDK 21)
# 1. ??š®ë±???<3F>ê¼<C3AA> (JDK 21)
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
# Gradle ?섑띁?€ ?뚯뒪 蹂듭궗
# Gradle ??ë<E28098><C3AB>?? ???’ª 蹂듭ê¶?
COPY gradlew .
COPY gradle gradle
COPY build.gradle settings.gradle ./
COPY src src
# 沅뚰븳 遺€??諛?鍮뚮뱶 (?뚯뒪???쒖쇅)
# æ²…ëš°ë¸??ºÂ€??è«???š®ë±?(???’ª????–쇅)
RUN chmod +x gradlew
RUN ./gradlew clean build -x test
# 2. ?ㅽ뻾 ?섍꼍 (JRE 21)
# 2. ??½ë»¾ ??<3F>ê¼<C3AA> (JRE 21)
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# ?€?꾩〈 ?ㅼ젙 (?쒓뎅 ?쒓컙)
# ???꾩ã€???¼ì ™ (??“뎅 ??“ì»™)
RUN apk add --no-cache tzdata
ENV TZ=Asia/Seoul
# 鍮뚮뱶??JAR ?뚯씪 蹂듭궗
# ??š®ë±??JAR ???”ª 蹂듭ê¶?
COPY --from=builder /app/build/libs/*.jar app.jar
# 湲곕낯 ?ы듃 ?몄텧
# 湲곕???????몄텧
EXPOSE 8081
# 而⑦뀒?대꼫 ?ㅽ뻾 ??JAR ?ㅽ뻾
# ?Œâ‘¦???€ê¼???½ë»¾ ??JAR ??½ë»¾
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@@ -1,3 +1,17 @@
/**
* @package io.shinhanlife
* @className McpBridge
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;

View File

@@ -45,6 +45,7 @@ DAP Backend는 2개의 주요 애플리케이션으로 분리 운영됩니다:
- `HR`: 휴가 등록, 연차 갯수 조회
- `CONTRACT`: 계약 상태, 계약 상세 조회
- `CUSTOMER`: 고객 등급, 고객 상세 정보 조회
- `SAMPLE`: 날씨, 환율, 명언 조회 등 외부 연동 샘플
- IntelliJ IDEA: `Run/Debug Configurations`에서 `DapTool*Application``Program arguments``--mcp.tool.target=NOTIFICATION` 입력
@@ -66,7 +67,7 @@ DAP Backend는 2개의 주요 애플리케이션으로 분리 운영됩니다:
}
}
```
- **특정 카테고리 툴 필터링**: `McpBridge.java` 내부의 URI 파라미터(`?categoryKey=common`)를 수정하여 원하는 도메인의 툴만 선택적으로 AI에게 학습시킬 수 있습니다.
- **특정 카테고리 툴 필터링**: `McpBridge.java` 내부의 URI 파라미터(`?categoryKey=sample` 등)를 수정하여 원하는 도메인의 툴만 선택적으로 AI에게 학습시킬 수 있습니다.
### 2. 프로덕션 클라우드 AI (Google Cloud Agent Builder 등) 연동
실제 라이브 서비스에서 동작하는 클라우드 Agent Builder는 REST API 기반의 OpenAPI Spec을 요구합니다.

125
add_javadoc.py Normal file
View File

@@ -0,0 +1,125 @@
import os
import re
JAVADOC_FILES = [
r'.\McpBridge.java',
r'.\dap-common\src\main\java\io\shinhanlife\dap\common\mcp\exception\GlobalExceptionHandler.java',
r'.\dap-common\src\main\java\io\shinhanlife\dap\common\util\ToolSourceUpdater.java',
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowAppServiceId.java',
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowControllerId.java',
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowIndexPaging.java',
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowMciFieldInfo.java',
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowMybatisMapper.java',
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowServiceGroupId.java',
r'.\dap-common\src\main\java\io\shinhanlife\glow\GlowTrgmField.java',
r'.\dap-common\src\test\java\io\shinhanlife\glow\util\GlowMciParserTest.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\DapGatewayApplication.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\audit\AuditLogService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\config\AgentResponseBudgetProperties.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\config\McpGatewayProperties.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\guardrail\GuardrailService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\guardrail\SensitiveDataMasker.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\guardrail\ToolResponseGuardrailService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\presentation\ChatController.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\presentation\McpRouterController.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\presentation\ScaffoldingController.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\redis\McpMonitorEventService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\redis\RedisToolTraceService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreaker.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreakerMonitorController.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreakerService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreakerSnapshot.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\CircuitBreakerState.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\FailureType.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\RetryPolicy.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\resilience\ToolExecutionException.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\security\McpRequestContext.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\security\McpRequestContextResolver.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\security\ToolAuthorizationService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\service\ExecuteService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\sync\DynamicMcpController.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\sync\DynamicMcpServerManager.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\sync\RegistryMcpToolSpecificationFactory.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\large\AgentResponseBudgetService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\large\LargeToolResponseService.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\large\PaginationRequestValidator.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\result\ToolExecutionResult.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\tool\result\ToolExecutionResultFormatter.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\transport\HttpToolInvoker.java',
r'.\dap-gateway\src\main\java\io\shinhanlife\dap\biz\mcp\gateway\transport\ToolInvoker.java',
r'.\dap-gateway\src\test\java\io\shinhanlife\dap\biz\mcp\gateway\DapGatewayApplicationTests.java',
r'.\dap-gateway\src\test\java\io\shinhanlife\dap\biz\mcp\gateway\tool\large\PaginationRequestValidatorTest.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\sample\converter\SampleLegacyConverter.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\sample\dto\SampleAiReqDto.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\annotation\McpFunction.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\annotation\McpParameter.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\annotation\McpTool.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\aop\ToolSlaMonitoringAspect.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\presentation\BusinessToolController.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\service\ToolRegistryHeartbeatSender.java',
r'.\dap-tool-core\src\main\java\io\shinhanlife\dap\biz\mcp\tool\util\JsonSchemaGenerator.java',
r'.\dap-tool-core\src\test\java\io\shinhanlife\dap\biz\mcp\sample\converter\SampleLegacyConverterTest.java',
r'.\dap-tool-email\src\main\java\io\shinhanlife\dap\biz\mcp\tool\email\DapToolEmailApplication.java',
r'.\dap-tool-other\src\main\java\io\shinhanlife\dap\biz\mcp\tool\dto\BalanceReq.java',
r'.\dap-tool-other\src\main\java\io\shinhanlife\dap\biz\mcp\tool\dto\BalanceRes.java',
r'.\dap-tool-other\src\main\java\io\shinhanlife\dap\biz\mcp\tool\other\DapToolOtherApplication.java',
r'.\dap-tool-other\src\main\java\io\shinhanlife\dap\biz\mcp\tool\service\BalanceService.java',
r'.\dap-tool-payment\src\main\java\io\shinhanlife\dap\biz\mcp\tool\payment\DapToolPaymentApplication.java',
r'.\dap-tool-sms\src\main\java\io\shinhanlife\dap\biz\mcp\tool\sms\DapToolSmsApplication.java',
r'.\dap-tool-sms\src\main\java\io\shinhanlife\dap\biz\mcp\tool\sms\converter\SmsLegacyConverter.java',
r'.\dap-tool-sms\src\main\java\io\shinhanlife\dap\biz\mcp\tool\sms\dto\SmsLegacyReqDto.java',
]
JAVADOC_TEMPLATE = '''/**
* @package {package}
* @className {classname}
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/'''
def get_package_and_class(content, filename):
pkg_match = re.search(r'^package\s+([^\s;]+)', content, re.MULTILINE)
package = pkg_match.group(1) if pkg_match else 'io.shinhanlife'
classname = os.path.splitext(filename)[0]
return package, classname
def add_javadoc(file_path):
if not os.path.exists(file_path):
print(f'SKIP (not found): {file_path}')
return
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
if '@className' in content or '@author' in content:
print(f'SKIP (already has javadoc): {file_path}')
return
filename = os.path.basename(file_path)
package, classname = get_package_and_class(content, filename)
javadoc = JAVADOC_TEMPLATE.format(package=package, classname=classname)
# Insert after package declaration
pkg_pattern = re.compile(r'^(package\s+[^\s;]+;\s*\n)', re.MULTILINE)
match = pkg_pattern.search(content)
if match:
insert_pos = match.end()
new_content = content[:insert_pos] + '\n' + javadoc + '\n' + content[insert_pos:]
else:
new_content = javadoc + '\n' + content
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f'OK: {file_path}')
for f in JAVADOC_FILES:
add_javadoc(f)
print('\nDone!')

View File

@@ -1,35 +0,0 @@
plugins {
id 'java-library'
}
dependencies {
api 'org.springframework.boot:spring-boot-starter-web'
api 'org.springframework.boot:spring-boot-starter-validation'
api 'org.springframework.boot:spring-boot-starter-data-redis'
api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
api 'io.github.resilience4j:resilience4j-core:2.2.0'
api 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
api 'io.github.resilience4j:resilience4j-ratelimiter'
api 'io.github.resilience4j:resilience4j-retry:2.2.0'
api 'org.springframework.boot:spring-boot-starter-aop:3.3.0'
api 'org.springframework.boot:spring-boot-starter-jdbc'
api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
api 'com.h2database:h2'
api 'p6spy:p6spy:3.9.1'
api 'io.lettuce:lettuce-core:6.6.0.RELEASE'
api "org.mapstruct:mapstruct:1.5.5.Final"
api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1'
api 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
api 'com.networknt:json-schema-validator:1.4.0'
api 'org.springframework.kafka:spring-kafka:3.2.0'
api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.32'
annotationProcessor 'org.projectlombok:lombok:1.18.32'
}

View File

@@ -1,40 +0,0 @@
package io.shinhanlife.dap.common.integration.mci.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @package io.shinhanlife.dap.common.integration.mci.dto
* @className ShinhanCommonHeaderDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ShinhanCommonHeaderDto {
private String envrTypeCd; // 환경유형코드 (D, T, R)
private String glbId; // 전사공통키 (37 Byte)
private String pgrsSriaNo; // 진행일련번호
private String trgmVrsnInfoValu; // 전문버전정보값
private String tgrmEncrYn; // 전문암호화여부
private String gpcpCd; // 글로벌법인코드
private String appliDutjCd; // 어플리케이션업무코드
private String rcvSvcId; // 수신서비스ID
private String reqRspnScCd; // 요청응답구분코드 (S:요청, R:응답)
private String inqrTraTypeCd; // 조회거래유형코드
private String reqTgrmTnsmDtptDt; // 요청전문전송일시
private String itrIfId; // 인터페이스ID
}

View File

@@ -1,13 +0,0 @@
package io.shinhanlife.glow;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GlowAppServiceId {
String value();
String description() default "";
}

View File

@@ -1,5 +0,0 @@
package io.shinhanlife.glow;
public @interface GlowControllerId {
String value();
}

View File

@@ -1,9 +0,0 @@
package io.shinhanlife.glow;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GlowIndexPaging {
}

View File

@@ -1,7 +0,0 @@
package io.shinhanlife.glow;
public @interface GlowServiceGroupId {
String value();
String description();
}

View File

@@ -3,7 +3,8 @@ WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle settings.gradle ./
COPY dap-common dap-common
COPY dap-tool-core dap-tool-core
COPY dap-gateway dap-gateway
RUN chmod +x gradlew
RUN ./gradlew :dap-gateway:build -x test

View File

@@ -3,7 +3,7 @@ plugins {
}
dependencies {
implementation project(':dap-common')
implementation project(':dap-tool-core')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

View File

@@ -1,15 +0,0 @@
package io.shinhanlife.dap.biz.mcp.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.biz.mcp.gateway", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.biz.mcp.gateway", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
@EnableCaching
public class DapGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(DapGatewayApplication.class, args);
}
}

View File

@@ -1,12 +0,0 @@
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
/**
* Circuit Breaker의 현재 상태입니다.
*
* CLOSED는 정상 호출 가능, OPEN은 호출 차단, HALF_OPEN은 복구 확인 상태를 의미합니다.
*/
public enum CircuitBreakerState {
CLOSED,
OPEN,
HALF_OPEN
}

View File

@@ -1,11 +0,0 @@
package io.shinhanlife.dap.biz.mcp.gateway.transport;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Map;
/**
* Tool 서버 호출 transport의 최소 공통 인터페이스입니다.
*/
public interface ToolInvoker {
JsonNode invoke(Map<String, Object> payload, String targetUrl);
}

View File

@@ -1,37 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.converter;
import io.shinhanlife.dap.biz.so.atm.dto.AthrOutDto;
import io.shinhanlife.dap.biz.so.atm.dto.AthrSaveInDto;
import io.shinhanlife.dap.biz.so.atm.dto.AthrSearchInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListInDto;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSaveRequest;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchRequest;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchResponse;
import io.shinhanlife.dap.biz.so.atm.presentation.io.RoleListRequest;
import org.mapstruct.Mapper;
/**
* @package io.shinhanlife.dap.biz.so.atm.converter
* @className AccessMgmtConverter
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Mapper(componentModel = "spring")
public abstract class AccessMgmtConverter {
public abstract RoleListInDto toInDto(RoleListRequest request);
public abstract AthrSearchInDto toInDto(AthrSearchRequest request);
public abstract AthrSaveInDto toInDto(AthrSaveRequest request);
public abstract AthrSearchResponse toResponse(AthrOutDto outDto);
}

View File

@@ -1,48 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.domain.repository;
import io.shinhanlife.dap.biz.so.atm.dto.AthrItemOutDto;
import io.shinhanlife.dap.biz.so.atm.dto.AthrSearchInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleKnwlAthrInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleToolAthrInDto;
import io.shinhanlife.glow.GlowMybatisMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.domain.repository
* @className AccessMgmtMapper
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@GlowMybatisMapper
public interface AccessMgmtMapper {
List<RoleListOutDto> selectRoles(RoleListInDto inDto);
List<AthrItemOutDto> selectAllTools();
List<AthrItemOutDto> selectAllKnwls();
List<String> selectGrantedToolIds(AthrSearchInDto inDto);
List<String> selectGrantedKnwlIds(AthrSearchInDto inDto);
void deleteToolAthr(@Param("systId") String systId, @Param("roleNo") String roleNo);
void insertToolAthr(RoleToolAthrInDto inDto);
void deleteKnwlAthr(@Param("systId") String systId, @Param("roleNo") String roleNo);
void insertKnwlAthr(RoleKnwlAthrInDto inDto);
}

View File

@@ -1,32 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.domain.service;
import io.shinhanlife.dap.biz.so.atm.dto.AthrOutDto;
import io.shinhanlife.dap.biz.so.atm.dto.AthrSaveInDto;
import io.shinhanlife.dap.biz.so.atm.dto.AthrSearchInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.domain.service
* @className AccessMgmtService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
public interface AccessMgmtService {
List<RoleListOutDto> getRoles(RoleListInDto inDto);
AthrOutDto getAthr(AthrSearchInDto inDto);
void saveAthr(AthrSaveInDto inDto);
}

View File

@@ -1,142 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.domain.service.impl;
import io.shinhanlife.dap.biz.so.atm.domain.repository.AccessMgmtMapper;
import io.shinhanlife.dap.biz.so.atm.domain.service.AccessMgmtService;
import io.shinhanlife.dap.biz.so.atm.dto.AthrItemOutDto;
import io.shinhanlife.dap.biz.so.atm.dto.AthrOutDto;
import io.shinhanlife.dap.biz.so.atm.dto.AthrSaveInDto;
import io.shinhanlife.dap.biz.so.atm.dto.AthrSearchInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleKnwlAthrInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListInDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
import io.shinhanlife.dap.biz.so.atm.dto.RoleToolAthrInDto;
import io.shinhanlife.dap.common.util.SessionUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @package io.shinhanlife.dap.biz.so.atm.domain.service.impl
* @className AccessMgmtServiceImpl
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Service
@RequiredArgsConstructor
public class AccessMgmtServiceImpl implements AccessMgmtService {
private final AccessMgmtMapper accessMgmtMapper;
private static final String SYSTEM_CD = "AXH";
private static final String SYSTEM_PRG = "SOATM0100";
@Override
public List<RoleListOutDto> getRoles(RoleListInDto inDto) {
return accessMgmtMapper.selectRoles(inDto);
}
@Override
public AthrOutDto getAthr(AthrSearchInDto inDto) {
Set<String> grantedToolIds = accessMgmtMapper.selectGrantedToolIds(inDto)
.stream().collect(Collectors.toSet());
Set<String> grantedKnwlIds = accessMgmtMapper.selectGrantedKnwlIds(inDto)
.stream().collect(Collectors.toSet());
List<AthrItemOutDto> tools = accessMgmtMapper.selectAllTools()
.stream()
.peek(item -> item.setGranted(grantedToolIds.contains(item.getResourceId())))
.collect(Collectors.toList());
List<AthrItemOutDto> knwls = accessMgmtMapper.selectAllKnwls()
.stream()
.peek(item -> item.setGranted(grantedKnwlIds.contains(item.getResourceId())))
.collect(Collectors.toList());
return new AthrOutDto(tools, knwls);
}
@Override
@Transactional
public void saveAthr(AthrSaveInDto inDto) {
Date now = new Date();
String systId = inDto.getSystId();
String roleNo = inDto.getRoleNo();
accessMgmtMapper.deleteToolAthr(systId, roleNo);
if (inDto.getGrantedToolIds() != null) {
for (String toolId : inDto.getGrantedToolIds()) {
accessMgmtMapper.insertToolAthr(buildToolAthrInDto(systId, roleNo, toolId, now));
}
}
accessMgmtMapper.deleteKnwlAthr(systId, roleNo);
if (inDto.getGrantedKnwlIds() != null) {
for (String knwlId : inDto.getGrantedKnwlIds()) {
accessMgmtMapper.insertKnwlAthr(buildKnwlAthrInDto(systId, roleNo, knwlId, now));
}
}
}
private RoleToolAthrInDto buildToolAthrInDto(String systId, String roleNo, String toolId, Date now) {
RoleToolAthrInDto dto = new RoleToolAthrInDto();
dto.setRoleToolAthrId("RTA-" + shortUuid());
dto.setSystId(systId);
dto.setRoleNo(roleNo);
dto.setToolId(toolId);
dto.setPuseYn("Y");
fillAudit(dto, now);
return dto;
}
private RoleKnwlAthrInDto buildKnwlAthrInDto(String systId, String roleNo, String knwlId, Date now) {
RoleKnwlAthrInDto dto = new RoleKnwlAthrInDto();
dto.setRoleKnwlAthrId("RKA-" + shortUuid());
dto.setSystId(systId);
dto.setRoleNo(roleNo);
dto.setKnwlId(knwlId);
dto.setPuseYn("Y");
fillAudit(dto, now);
return dto;
}
private void fillAudit(RoleToolAthrInDto dto, Date now) {
String prafNo = SessionUtil.getPrafNo();
String ognzNo = SessionUtil.getOgnzNo();
dto.setSystRgiDt(now); dto.setSystRgiPrafNo(prafNo);
dto.setSystRgiOgnzNo(ognzNo); dto.setSystRgiSystCd(SYSTEM_CD);
dto.setSystRgiPrgrId(SYSTEM_PRG);
dto.setSystChgDt(now); dto.setSystChgPrafNo(prafNo);
dto.setSystChgOgnzNo(ognzNo); dto.setSystChgSystCd(SYSTEM_CD);
dto.setSystChgPrgrId(SYSTEM_PRG);
}
private void fillAudit(RoleKnwlAthrInDto dto, Date now) {
String prafNo = SessionUtil.getPrafNo();
String ognzNo = SessionUtil.getOgnzNo();
dto.setSystRgiDt(now); dto.setSystRgiPrafNo(prafNo);
dto.setSystRgiOgnzNo(ognzNo); dto.setSystRgiSystCd(SYSTEM_CD);
dto.setSystRgiPrgrId(SYSTEM_PRG);
dto.setSystChgDt(now); dto.setSystChgPrafNo(prafNo);
dto.setSystChgOgnzNo(ognzNo); dto.setSystChgSystCd(SYSTEM_CD);
dto.setSystChgPrgrId(SYSTEM_PRG);
}
private String shortUuid() {
return UUID.randomUUID().toString().replace("-", "").substring(0, 12).toUpperCase();
}
}

View File

@@ -1,35 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.dto;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
* @package io.shinhanlife.dap.biz.so.atm.dto
* @className AthrItemOutDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AthrItemOutDto {
private String resourceId;
private String resourceNm;
private String resourceDs;
private String typeCode;
private boolean granted;
}

View File

@@ -1,34 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.dto;
import lombok.Setter;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.dto
* @className AthrOutDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@AllArgsConstructor
@Setter
@Builder
@NoArgsConstructor
public class AthrOutDto {
private List<AthrItemOutDto> tools;
private List<AthrItemOutDto> knwls;
}

View File

@@ -1,36 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.dto;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.dto
* @className AthrSaveInDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AthrSaveInDto {
private String systId;
private String roleNo;
private List<String> grantedToolIds;
private List<String> grantedKnwlIds;
}

View File

@@ -1,32 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.dto;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
* @package io.shinhanlife.dap.biz.so.atm.dto
* @className AthrSearchInDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AthrSearchInDto {
private String systId;
private String roleNo;
}

View File

@@ -1,48 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.dto;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @package io.shinhanlife.dap.biz.so.atm.dto
* @className RoleKnwlAthrInDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleKnwlAthrInDto {
private String roleKnwlAthrId;
private String systId;
private String roleNo;
private String knwlId;
private String puseYn;
private Date systRgiDt;
private String systRgiPrafNo;
private String systRgiOgnzNo;
private String systRgiSystCd;
private String systRgiPrgrId;
private Date systChgDt;
private String systChgPrafNo;
private String systChgOgnzNo;
private String systChgSystCd;
private String systChgPrgrId;
}

View File

@@ -1,33 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.dto;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
* @package io.shinhanlife.dap.biz.so.atm.dto
* @className RoleListInDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleListInDto {
private String systId;
private String puseYn;
private String keyword;
}

View File

@@ -1,35 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.dto;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
* @package io.shinhanlife.dap.biz.so.atm.dto
* @className RoleListOutDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleListOutDto {
private String systId;
private String roleNo;
private String roleNm;
private String roleDs;
private String puseYn;
}

View File

@@ -1,48 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.dto;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @package io.shinhanlife.dap.biz.so.atm.dto
* @className RoleToolAthrInDto
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleToolAthrInDto {
private String roleToolAthrId;
private String systId;
private String roleNo;
private String toolId;
private String puseYn;
private Date systRgiDt;
private String systRgiPrafNo;
private String systRgiOgnzNo;
private String systRgiSystCd;
private String systRgiPrgrId;
private Date systChgDt;
private String systChgPrafNo;
private String systChgOgnzNo;
private String systChgSystCd;
private String systChgPrgrId;
}

View File

@@ -1,59 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.presentation;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSaveRequest;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchRequest;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchResponse;
import io.shinhanlife.dap.biz.so.atm.presentation.io.RoleListRequest;
import io.shinhanlife.dap.biz.so.atm.usecase.AccessMgmtUseCase;
import io.shinhanlife.glow.BaseResponse;
import io.shinhanlife.glow.ResponseUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.presentation
* @className SOATM0100Controller
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@RestController
@RequestMapping("/so/atm")
@RequiredArgsConstructor
public class SOATM0100Controller {
private final AccessMgmtUseCase accessMgmtUseCase;
@PostMapping("/SOATM0100R")
public ResponseEntity<BaseResponse<List<RoleListOutDto>>> getRoles(
@RequestBody RoleListRequest request) {
return ResponseUtil.ok(accessMgmtUseCase.getRoles(request));
}
@PostMapping("/SOATM0101R")
public ResponseEntity<BaseResponse<AthrSearchResponse>> getAthr(
@RequestBody AthrSearchRequest request) {
return ResponseUtil.ok(accessMgmtUseCase.getAthr(request));
}
@PostMapping("/SOATM0100U")
public ResponseEntity<BaseResponse<Void>> saveAthr(
@RequestBody AthrSaveRequest request) {
accessMgmtUseCase.saveAthr(request);
return ResponseUtil.ok();
}
}

View File

@@ -1,29 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.presentation.io;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.presentation.io
* @className AthrSaveRequest
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
public class AthrSaveRequest {
private String systId;
private String roleNo;
private List<String> grantedToolIds;
private List<String> grantedKnwlIds;
}

View File

@@ -1,28 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.presentation.io;
import io.shinhanlife.dap.biz.so.atm.dto.AthrItemOutDto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.presentation.io
* @className AthrSearchResponse
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@AllArgsConstructor
public class AthrSearchResponse {
private List<AthrItemOutDto> tools;
private List<AthrItemOutDto> knwls;
}

View File

@@ -1,26 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.presentation.io;
import lombok.Getter;
import lombok.Setter;
/**
* @package io.shinhanlife.dap.biz.so.atm.presentation.io
* @className RoleListRequest
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Getter
@Setter
public class RoleListRequest {
private String systId;
private String puseYn;
private String keyword;
}

View File

@@ -1,32 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.usecase;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSaveRequest;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchRequest;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchResponse;
import io.shinhanlife.dap.biz.so.atm.presentation.io.RoleListRequest;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.usecase
* @className AccessMgmtUseCase
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
public interface AccessMgmtUseCase {
List<RoleListOutDto> getRoles(RoleListRequest request);
AthrSearchResponse getAthr(AthrSearchRequest request);
void saveAthr(AthrSaveRequest request);
}

View File

@@ -1,51 +0,0 @@
package io.shinhanlife.dap.biz.so.atm.usecase.impl;
import io.shinhanlife.dap.biz.so.atm.converter.AccessMgmtConverter;
import io.shinhanlife.dap.biz.so.atm.domain.service.AccessMgmtService;
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSaveRequest;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchRequest;
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchResponse;
import io.shinhanlife.dap.biz.so.atm.presentation.io.RoleListRequest;
import io.shinhanlife.dap.biz.so.atm.usecase.AccessMgmtUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.so.atm.usecase.impl
* @className AccessMgmtUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
@Service
@RequiredArgsConstructor
public class AccessMgmtUseCaseImpl implements AccessMgmtUseCase {
private final AccessMgmtService accessMgmtService;
private final AccessMgmtConverter converter;
@Override
public List<RoleListOutDto> getRoles(RoleListRequest request) {
return accessMgmtService.getRoles(converter.toInDto(request));
}
@Override
public AthrSearchResponse getAthr(AthrSearchRequest request) {
return converter.toResponse(accessMgmtService.getAthr(converter.toInDto(request)));
}
@Override
public void saveAthr(AthrSaveRequest request) {
accessMgmtService.saveAthr(converter.toInDto(request));
}
}

View File

@@ -0,0 +1,30 @@
package io.shinhanlife.dap.mcg;
/**
* @package io.shinhanlife.dap.mcg
* @className DapGatewayApplication
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcg", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcg", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
@EnableCaching
public class DapGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(DapGatewayApplication.class, args);
}
}

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.dap.biz.mcp.gateway.aop;
package io.shinhanlife.dap.mcg.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
@@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.aop
* @package io.shinhanlife.dap.mcg.aop
* @className GatewayLoggingAspect
* @description AX HUB 시스템 처리 클래스
* @author 김형식
@@ -27,7 +27,7 @@ import org.springframework.util.StopWatch;
public class GatewayLoggingAspect {
// gateway의 controller 패키지 하위의 모든 클래스/메서드 실행 작동
@Around("execution(* io.shinhanlife.dap.biz.mcp.gateway.presentation..*(..))")
@Around("execution(* io.shinhanlife.dap.mcg.presentation..*(..))")
public Object logGatewayExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
String targetMethod = joinPoint.getSignature().toShortString();
StopWatch stopWatch = new StopWatch();

View File

@@ -1,8 +1,23 @@
package io.shinhanlife.dap.biz.mcp.gateway.audit;
package io.shinhanlife.dap.mcg.audit;
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
import io.shinhanlife.dap.biz.mcp.gateway.security.McpRequestContext;
/**
* @package io.shinhanlife.dap.mcg.audit
* @className AuditLogService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker;
import io.shinhanlife.dap.mcg.security.McpRequestContext;
import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.config;
package io.shinhanlife.dap.mcg.config;
/**
* @package io.shinhanlife.dap.mcg.config
* @className AgentResponseBudgetProperties
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.dap.biz.mcp.gateway.config;
package io.shinhanlife.dap.mcg.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -8,7 +8,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.config
* @package io.shinhanlife.dap.mcg.config
* @className GatewayFallbackProperties
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.config;
package io.shinhanlife.dap.mcg.config;
/**
* @package io.shinhanlife.dap.mcg.config
* @className McpGatewayProperties
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

View File

@@ -1,6 +1,6 @@
package io.shinhanlife.dap.biz.mcp.gateway.config;
package io.shinhanlife.dap.mcg.config;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -10,7 +10,7 @@ import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.config
* @package io.shinhanlife.dap.mcg.config
* @className RedisConfig
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,7 +1,7 @@
package io.shinhanlife.dap.biz.mcp.gateway.dto;
package io.shinhanlife.dap.mcg.dto;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.dto
* @package io.shinhanlife.dap.mcg.dto
* @className OperationType
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.dap.biz.mcp.gateway.dto;
package io.shinhanlife.dap.mcg.dto;
import lombok.Getter;
import lombok.Setter;
@@ -17,7 +17,7 @@ import java.util.HashSet;
* Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan) 사용됩니다.
*/
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.dto
* @package io.shinhanlife.dap.mcg.dto
* @className ToolMetadata
* @description AX HUB 시스템 처리 클래스
* @author 김형식
@@ -71,6 +71,18 @@ public class ToolMetadata {
@Builder.Default
private Boolean requiresApproval = false;
@Builder.Default
private Boolean readOnlyHint = false;
@Builder.Default
private Boolean destructiveHint = false;
@Builder.Default
private Boolean idempotentHint = false;
@Builder.Default
private Boolean openWorldHint = false;
// 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI)
@@ -113,4 +125,4 @@ public class ToolMetadata {
if (parametersSchema == null || !parametersSchema.containsKey("required")) return Set.of();
return new HashSet<>((List<String>) parametersSchema.get("required"));
}
}
}

View File

@@ -1,8 +1,23 @@
package io.shinhanlife.dap.biz.mcp.gateway.guardrail;
package io.shinhanlife.dap.mcg.guardrail;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
/**
* @package io.shinhanlife.dap.mcg.guardrail
* @className GuardrailService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.resilience.FailureType;
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Service;

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.guardrail;
package io.shinhanlife.dap.mcg.guardrail;
/**
* @package io.shinhanlife.dap.mcg.guardrail
* @className SensitiveDataMasker
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -1,8 +1,23 @@
package io.shinhanlife.dap.biz.mcp.gateway.guardrail;
package io.shinhanlife.dap.mcg.guardrail;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
/**
* @package io.shinhanlife.dap.mcg.guardrail
* @className ToolResponseGuardrailService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.resilience.FailureType;
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.dap.biz.mcp.gateway.messaging;
package io.shinhanlife.dap.mcg.messaging;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -10,7 +10,7 @@ import java.util.Map;
import java.util.UUID;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.messaging
* @package io.shinhanlife.dap.mcg.messaging
* @className KafkaProducerService
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,8 +1,23 @@
package io.shinhanlife.dap.biz.mcp.gateway.presentation;
package io.shinhanlife.dap.mcg.presentation;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.biz.mcp.gateway.registry.RedisRegistryService;
import io.shinhanlife.dap.biz.mcp.gateway.service.ExecuteService;
/**
* @package io.shinhanlife.dap.mcg.presentation
* @className ChatController
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
import io.shinhanlife.dap.mcg.service.ExecuteService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;

View File

@@ -1,6 +1,6 @@
package io.shinhanlife.dap.biz.mcp.gateway.presentation;
package io.shinhanlife.dap.mcg.presentation;
import io.shinhanlife.dap.biz.mcp.gateway.service.KillSwitchService;
import io.shinhanlife.dap.mcg.service.KillSwitchService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.presentation
* @package io.shinhanlife.dap.mcg.presentation
* @className KillSwitchController
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,13 +1,28 @@
package io.shinhanlife.dap.biz.mcp.gateway.presentation;
package io.shinhanlife.dap.mcg.presentation;
/**
* @package io.shinhanlife.dap.mcg.presentation
* @className McpRouterController
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import com.fasterxml.jackson.databind.ObjectMapper;
import io.shinhanlife.dap.biz.mcp.adapter.dto.JsonRpcRequest;
import io.shinhanlife.dap.biz.mcp.adapter.dto.JsonRpcResponse;
import io.shinhanlife.dap.biz.mcp.adapter.dto.Params;
import io.shinhanlife.dap.biz.mcp.gateway.config.GatewayFallbackProperties;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.biz.mcp.gateway.registry.RedisRegistryService;
import io.shinhanlife.dap.biz.mcp.gateway.service.ExecuteService;
import io.shinhanlife.dap.common.adapter.dto.JsonRpcRequest;
import io.shinhanlife.dap.common.adapter.dto.JsonRpcResponse;
import io.shinhanlife.dap.common.adapter.dto.Params;
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
import io.shinhanlife.dap.mcg.service.ExecuteService;
import io.shinhanlife.dap.common.mcp.security.SecurityProperties;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -76,7 +91,7 @@ public class McpRouterController {
try {
Object result = executeService.execute(payload, effectiveTenantId);
io.shinhanlife.dap.biz.mcp.adapter.dto.JsonRpcResponse response = new io.shinhanlife.dap.biz.mcp.adapter.dto.JsonRpcResponse();
io.shinhanlife.dap.common.adapter.dto.JsonRpcResponse response = new io.shinhanlife.dap.common.adapter.dto.JsonRpcResponse();
response.setJsonrpc("2.0");
response.setId(payload.containsKey("id") ? String.valueOf(payload.get("id")) : UUID.randomUUID().toString());
response.setResult(result);

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.presentation;
package io.shinhanlife.dap.mcg.presentation;
/**
* @package io.shinhanlife.dap.mcg.presentation
* @className ScaffoldingController
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.common.util.PodScaffolder;
import io.shinhanlife.dap.common.util.ToolScaffolder;
import io.shinhanlife.dap.common.util.ToolSourceUpdater;

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.redis;
package io.shinhanlife.dap.mcg.redis;
/**
* @package io.shinhanlife.dap.mcg.redis
* @className McpMonitorEventService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

View File

@@ -1,9 +1,24 @@
package io.shinhanlife.dap.biz.mcp.gateway.redis;
package io.shinhanlife.dap.mcg.redis;
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
import io.shinhanlife.dap.biz.mcp.gateway.security.McpRequestContext;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
/**
* @package io.shinhanlife.dap.mcg.redis
* @className RedisToolTraceService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker;
import io.shinhanlife.dap.mcg.security.McpRequestContext;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;

View File

@@ -1,6 +1,6 @@
package io.shinhanlife.dap.biz.mcp.gateway.registry;
package io.shinhanlife.dap.mcg.registry;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
@@ -13,7 +13,7 @@ import java.util.stream.Collectors;
import java.util.Objects;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.registry
* @package io.shinhanlife.dap.mcg.registry
* @className RedisRegistryService
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
package io.shinhanlife.dap.mcg.resilience;
/**
* @package io.shinhanlife.dap.mcg.resilience
* @className CircuitBreaker
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import java.time.Clock;
import java.time.Instant;

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
package io.shinhanlife.dap.mcg.resilience;
/**
* @package io.shinhanlife.dap.mcg.resilience
* @className CircuitBreakerMonitorController
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

View File

@@ -1,6 +1,21 @@
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
package io.shinhanlife.dap.mcg.resilience;
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
/**
* @package io.shinhanlife.dap.mcg.resilience
* @className CircuitBreakerService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
import org.springframework.stereotype.Service;
import java.time.Clock;

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
package io.shinhanlife.dap.mcg.resilience;
/**
* @package io.shinhanlife.dap.mcg.resilience
* @className CircuitBreakerSnapshot
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
/**
* MCP Monitor 화면에 내려주는 Circuit Breaker 상태 정보입니다.
*/

View File

@@ -0,0 +1,27 @@
package io.shinhanlife.dap.mcg.resilience;
/**
* @package io.shinhanlife.dap.mcg.resilience
* @className CircuitBreakerState
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
/**
* Circuit Breaker의 현재 상태입니다.
*
* CLOSED는 정상 호출 가능, OPEN은 호출 차단, HALF_OPEN은 복구 확인 상태를 의미합니다.
*/
public enum CircuitBreakerState {
CLOSED,
OPEN,
HALF_OPEN
}

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
package io.shinhanlife.dap.mcg.resilience;
/**
* @package io.shinhanlife.dap.mcg.resilience
* @className FailureType
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
/**
* Tool/EIMS/Tool 서버 호출 실패를 업무적으로 구분하기 위한 장애 유형입니다.
*

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
package io.shinhanlife.dap.mcg.resilience;
/**
* @package io.shinhanlife.dap.mcg.resilience
* @className RetryPolicy
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
/**
* Tool 호출 재시도 정책입니다.
*

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
package io.shinhanlife.dap.mcg.resilience;
/**
* @package io.shinhanlife.dap.mcg.resilience
* @className ToolExecutionException
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
/**
* Tool 실행 발생한 오류를 FailureType과 함께 전달하는 공통 예외입니다.
*

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.security;
package io.shinhanlife.dap.mcg.security;
/**
* @package io.shinhanlife.dap.mcg.security
* @className McpRequestContext
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import java.util.Set;
/**

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.security;
package io.shinhanlife.dap.mcg.security;
/**
* @package io.shinhanlife.dap.mcg.security
* @className McpRequestContextResolver
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;

View File

@@ -1,10 +1,25 @@
package io.shinhanlife.dap.biz.mcp.gateway.security;
package io.shinhanlife.dap.mcg.security;
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
import io.shinhanlife.dap.biz.mcp.gateway.dto.OperationType;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
/**
* @package io.shinhanlife.dap.mcg.security
* @className ToolAuthorizationService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
import io.shinhanlife.dap.mcg.resilience.FailureType;
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
import io.shinhanlife.dap.mcg.dto.OperationType;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import org.springframework.stereotype.Service;
import java.util.Set;

View File

@@ -1,20 +1,37 @@
package io.shinhanlife.dap.biz.mcp.gateway.service;
package io.shinhanlife.dap.mcg.service;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.RetryPolicy;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
import io.shinhanlife.dap.biz.mcp.gateway.dto.OperationType;
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.GuardrailService;
import io.shinhanlife.dap.biz.mcp.gateway.security.McpRequestContext;
import io.shinhanlife.dap.biz.mcp.gateway.security.McpRequestContextResolver;
import io.shinhanlife.dap.biz.mcp.gateway.audit.AuditLogService;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.CircuitBreaker;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.CircuitBreakerService;
import io.shinhanlife.dap.biz.mcp.gateway.security.ToolAuthorizationService;
import io.shinhanlife.dap.biz.mcp.gateway.redis.RedisToolTraceService;
/**
* @package io.shinhanlife.dap.mcg.service
* @className ExecuteService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import java.util.HashMap;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.resilience.FailureType;
import io.shinhanlife.dap.mcg.resilience.RetryPolicy;
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
import io.shinhanlife.dap.mcg.dto.OperationType;
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker;
import io.shinhanlife.dap.mcg.guardrail.GuardrailService;
import io.shinhanlife.dap.mcg.security.McpRequestContext;
import io.shinhanlife.dap.mcg.security.McpRequestContextResolver;
import io.shinhanlife.dap.mcg.audit.AuditLogService;
import io.shinhanlife.dap.mcg.resilience.CircuitBreaker;
import io.shinhanlife.dap.mcg.resilience.CircuitBreakerService;
import io.shinhanlife.dap.mcg.security.ToolAuthorizationService;
import io.shinhanlife.dap.mcg.redis.RedisToolTraceService;
import jakarta.annotation.PreDestroy;
import java.util.Map;
@@ -27,12 +44,12 @@ import org.springframework.web.client.RestClient;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.shinhanlife.dap.biz.mcp.gateway.tool.large.LargeToolResponseService;
import io.shinhanlife.dap.biz.mcp.gateway.tool.large.PaginationRequestValidator;
import io.shinhanlife.dap.biz.mcp.gateway.tool.result.ToolExecutionResultFormatter;
import io.shinhanlife.dap.biz.mcp.gateway.tool.result.ToolExecutionResult;
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.ToolResponseGuardrailService;
import io.shinhanlife.dap.biz.mcp.gateway.transport.ToolInvoker;
import io.shinhanlife.dap.mcg.tool.large.LargeToolResponseService;
import io.shinhanlife.dap.mcg.tool.large.PaginationRequestValidator;
import io.shinhanlife.dap.mcg.tool.result.ToolExecutionResultFormatter;
import io.shinhanlife.dap.mcg.tool.result.ToolExecutionResult;
import io.shinhanlife.dap.mcg.guardrail.ToolResponseGuardrailService;
import io.shinhanlife.dap.mcg.transport.ToolInvoker;
@Slf4j
@Service
@@ -145,22 +162,53 @@ public class ExecuteService {
long elapsedMillis = elapsedMillis(startedAt);
String responseText = "";
try { responseText = objectMapper.writeValueAsString(result); } catch (Exception ignore) {}
long originalSize = 0;
try {
responseText = objectMapper.writeValueAsString(result);
originalSize = responseText.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
} catch (Exception ignore) {}
Map<String, Object> finalResult = new java.util.LinkedHashMap<>();
finalResult.put("status", "ok");
finalResult.put("result", result);
finalResult.put("error_code", null);
finalResult.put("error_message", null);
finalResult.put("elapsed_ms", elapsedMillis);
finalResult.put("truncated", false);
finalResult.put("original_size", originalSize);
auditLogService.toolFinished(context, metadata.getName(), elapsedMillis, true, "");
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, true, "", responseText);
return result;
return finalResult;
} catch (ToolExecutionException error) {
long elapsedMillis = elapsedMillis(startedAt);
auditLogService.toolFinished(context, toolName, elapsedMillis, false, error.failureType().name());
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, error.failureType().name(), "");
throw error;
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
errorResult.put("status", "error");
errorResult.put("result", null);
errorResult.put("error_code", error.failureType().name());
errorResult.put("error_message", error.getMessage());
errorResult.put("elapsed_ms", elapsedMillis);
errorResult.put("truncated", false);
errorResult.put("original_size", 0);
return errorResult;
} catch (Exception error) {
long elapsedMillis = elapsedMillis(startedAt);
auditLogService.toolFinished(context, toolName, elapsedMillis, false, FailureType.INTERNAL_ERROR.name());
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, FailureType.INTERNAL_ERROR.name(), "");
throw new ToolExecutionException(FailureType.INTERNAL_ERROR, "Tool execution failed: " + toolName, error);
Map<String, Object> errorResult = new java.util.LinkedHashMap<>();
errorResult.put("status", "error");
errorResult.put("result", null);
errorResult.put("error_code", FailureType.INTERNAL_ERROR.name());
errorResult.put("error_message", error.getMessage());
errorResult.put("elapsed_ms", elapsedMillis);
errorResult.put("truncated", false);
errorResult.put("original_size", 0);
return errorResult;
}
}
@@ -213,20 +261,17 @@ public class ExecuteService {
if (metadata.getPodUrl() != null && !metadata.getPodUrl().isEmpty()) {
targetUrl = metadata.getPodUrl();
}
String executeApiUrl = targetUrl + "/mcp/api/v1/tools/call";
String executeApiUrl = targetUrl + "/mcp/" + metadata.getName();
Map<String, String> headers = new java.util.HashMap<>();
headers.put("trace-id", context.requestId());
headers.put("request-id", java.util.UUID.randomUUID().toString());
ObjectNode pageArguments = paginationValidator.normalize(arguments);
LargeToolResponseService.Collector collector = largeResponses.newCollector(metadata.getName(), context.requestId());
while (true) {
Map<String, Object> pagePayload = new java.util.HashMap<>(payload);
if (pagePayload.containsKey("params")) {
Map<String, Object> params = new java.util.HashMap<>((Map<String, Object>) pagePayload.get("params"));
params.put("arguments", objectMapper.convertValue(pageArguments, Map.class));
pagePayload.put("params", params);
} else {
pagePayload.put("arguments", objectMapper.convertValue(pageArguments, Map.class));
}
Map<String, Object> pagePayload = objectMapper.convertValue(pageArguments, Map.class);
try {
log.info(" [ExecuteService] 요청 페이로드(마스킹 적용): {}", objectMapper.writeValueAsString(dataMasker.mask(objectMapper.valueToTree(pagePayload))));
@@ -234,13 +279,16 @@ public class ExecuteService {
JsonNode data = null;
try {
data = toolInvoker.invoke(pagePayload, executeApiUrl);
data = toolInvoker.invoke(pagePayload, executeApiUrl, headers);
} catch (org.springframework.web.client.RestClientResponseException e) {
// HTTP 4xx, 5xx 에러는 연결 오류가 아니라 비즈니스 로직 오류이거나 검증 실패이므로 원본 에러를 그대로 반환
throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod HTTP 에러 (" + e.getStatusCode() + "): " + e.getResponseBodyAsString());
} catch (Exception e) {
if (executeApiUrl.contains("http://tool-")) {
String fallbackUrl = executeApiUrl.replaceAll("http://tool-[a-zA-Z0-9-]+", "http://localhost");
log.warn(" [ExecuteService] 호스트를 찾을 수 없어 localhost로 재시도합니다: {}", fallbackUrl);
try {
data = toolInvoker.invoke(pagePayload, fallbackUrl);
data = toolInvoker.invoke(pagePayload, fallbackUrl, headers);
} catch (Exception ex) {
throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod 호출 실패 (localhost 재시도 포함): " + ex.getMessage());
}

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.dap.biz.mcp.gateway.service;
package io.shinhanlife.dap.mcg.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -6,7 +6,7 @@ import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.service
* @package io.shinhanlife.dap.mcg.service
* @className KillSwitchService
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,9 +1,9 @@
package io.shinhanlife.dap.biz.mcp.gateway.service;
package io.shinhanlife.dap.mcg.service;
import io.shinhanlife.dap.common.mcp.security.SecurityProperties;
import io.shinhanlife.dap.biz.mcp.gateway.registry.RedisRegistryService;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.biz.mcp.gateway.config.GatewayFallbackProperties;
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -12,7 +12,7 @@ import java.util.List;
import java.util.Map;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.service
* @package io.shinhanlife.dap.mcg.service
* @className ToolPlanner
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,15 +1,36 @@
package io.shinhanlife.dap.biz.mcp.gateway.sync;
package io.shinhanlife.dap.mcg.sync;
/**
* @package io.shinhanlife.dap.mcg.sync
* @className CustomWebMvcSseServerTransportProvider
* @description AX HUB MCP Gateway SSE 전송 제공자 - SSE 기반의 MCP 서버 트랜스포트를 구현하는 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCNotification;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse;
import io.modelcontextprotocol.spec.McpServerSession;
import io.modelcontextprotocol.spec.McpServerTransport;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Mono;
import java.util.Map;
@@ -28,6 +49,7 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
private final String sseEndpoint;
private final String messageEndpoint;
private final Map<String, McpServerSession> sessions = new ConcurrentHashMap<>();
private final Map<String, CustomMcpSessionTransport> customTransports = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper;
public CustomWebMvcSseServerTransportProvider(String sseEndpoint, String messageEndpoint, ObjectMapper objectMapper) {
@@ -50,6 +72,7 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
} catch (Exception ignored) {}
});
sessions.clear();
customTransports.clear();
});
}
@@ -60,30 +83,30 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
.toList());
}
public org.springframework.web.servlet.mvc.method.annotation.SseEmitter handleSse() {
public SseEmitter handleSse() {
if (sessionFactory == null) {
throw new IllegalStateException("SessionFactory not configured");
}
org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter = new org.springframework.web.servlet.mvc.method.annotation.SseEmitter(-1L);
SseEmitter emitter = new SseEmitter(-1L);
String sessionId = UUID.randomUUID().toString();
CustomMcpSessionTransport sessionTransport = new CustomMcpSessionTransport(emitter, sessionId);
McpServerSession session = sessionFactory.create(sessionTransport);
sessions.put(sessionId, session);
emitter.onCompletion(() -> sessions.remove(sessionId));
emitter.onTimeout(() -> sessions.remove(sessionId));
new Thread(() -> {
try {
Thread.sleep(100);
emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event().name("endpoint").data(messageEndpoint + "?sessionId=" + sessionId));
emitter.send(SseEmitter.event().name("endpoint").data(messageEndpoint + "?sessionId=" + sessionId));
} catch (Exception e) {
emitter.completeWithError(e);
}
}).start();
return emitter;
}
@@ -94,22 +117,29 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter = new org.springframework.web.servlet.mvc.method.annotation.SseEmitter(-1L);
CustomMcpSessionTransport sessionTransport = new CustomMcpSessionTransport(emitter, sessionId);
McpServerSession session = sessionFactory.create(sessionTransport);
sessions.put(sessionId, session);
boolean isNew = !sessions.containsKey(sessionId);
emitter.onCompletion(() -> sessions.remove(sessionId));
emitter.onTimeout(() -> sessions.remove(sessionId));
if (isNew) {
CustomMcpSessionTransport sessionTransport = new CustomMcpSessionTransport(emitter, sessionId);
customTransports.put(sessionId, sessionTransport);
McpServerSession session = sessionFactory.create(sessionTransport);
sessions.put(sessionId, session);
// 주의: 클라이언트가 단일 POST 응답 연결을 끊더라도,
// (Warm Pool) 스펙상 세션은 살려둬야 하므로 세션 삭제 로직 제외
} else {
// 기존 세션인 경우 Emitter 파이프만 덮어씌움 (Switching)
CustomMcpSessionTransport sessionTransport = customTransports.get(sessionId);
if (sessionTransport != null) {
sessionTransport.setEmitter(emitter);
}
}
new Thread(() -> {
try {
// 커스텀 클라이언트는 endpoint 이벤트를 무시할 있지만, 표준 호환성을 위해 전송
Thread.sleep(100);
emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event().name("endpoint").data(messageEndpoint + "?sessionId=" + sessionId));
// Body로 들어온 initialize 즉시 처리
// Body로 들어온 메시지 즉시 비동기 처리
if (body != null && !body.trim().isEmpty()) {
handleMessage(sessionId, body);
handleMessage(sessionId, body, emitter);
}
} catch (Exception e) {
emitter.completeWithError(e);
@@ -120,38 +150,52 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
}
public org.springframework.http.ResponseEntity<String> handleMessage(String sessionId, String body) {
return handleMessage(sessionId, body, null);
}
public org.springframework.http.ResponseEntity<String> handleMessage(String sessionId, String body, org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter) {
log.info("Received POST message for sessionId: " + sessionId + ", body: " + body);
if (sessionId == null || !sessions.containsKey(sessionId)) {
return org.springframework.http.ResponseEntity.badRequest().body("Missing or invalid sessionId");
return ResponseEntity.badRequest().body("Unexpected request body");
}
McpServerSession session = sessions.get(sessionId);
try {
java.util.Map<String, Object> map = objectMapper.readValue(body, new com.fasterxml.jackson.core.type.TypeReference<java.util.Map<String, Object>>() {});
io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage message;
Map<String, Object> map = objectMapper.readValue(body, new TypeReference<Map<String, Object>>() {});
JSONRPCMessage message;
if (map.containsKey("id")) {
if (map.containsKey("method")) {
message = objectMapper.convertValue(map, io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest.class);
message = objectMapper.convertValue(map, JSONRPCRequest.class);
} else {
message = objectMapper.convertValue(map, io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse.class);
message = objectMapper.convertValue(map, JSONRPCResponse.class);
}
} else {
message = objectMapper.convertValue(map, io.modelcontextprotocol.spec.McpSchema.JSONRPCNotification.class);
message = objectMapper.convertValue(map, JSONRPCNotification.class);
}
log.info("Converted message type: " + message.getClass().getName());
session.handle(message).subscribe();
log.info("Message sent to session handler");
return org.springframework.http.ResponseEntity.ok().build();
if (emitter != null && !map.containsKey("id")) {
emitter.complete();
log.info("Completed emitter for notification");
}
return ResponseEntity.ok().build();
} catch (Exception e) {
log.error("Failed to handle message", e);
return org.springframework.http.ResponseEntity.status(500).body(e.getMessage());
return ResponseEntity.status(500).body(e.getMessage());
}
}
public boolean hasSession(String sessionId) {
return sessionId != null && sessions.containsKey(sessionId);
}
private class CustomMcpSessionTransport implements McpServerTransport {
private final org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter;
private volatile org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter;
private final String sessionId;
public CustomMcpSessionTransport(org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter, String sessionId) {
@@ -159,6 +203,10 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
this.sessionId = sessionId;
}
public void setEmitter(org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter) {
this.emitter = emitter;
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.fromRunnable(() -> {
@@ -166,18 +214,30 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
try {
String json = objectMapper.writeValueAsString(message);
log.info("Serialized message: " + json);
emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event().name("message").data(json));
log.info("Message successfully sent to SSE emitter");
if (this.emitter != null) {
this.emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event().name("message").data(json));
log.info("Message successfully sent to SSE emitter");
// Custom 프로토콜: 1회 요청당 1응답 종료 (스트림을 닫아버림)
// 클라이언트가 번의 POST 응답을 받고 연결을 끊기 때문
this.emitter.complete();
}
} catch (Exception e) {
log.error("Error sending message to SSE emitter", e);
emitter.completeWithError(e);
if (this.emitter != null) {
this.emitter.completeWithError(e);
}
}
});
}
@Override
public Mono<Void> closeGracefully() {
return Mono.fromRunnable(emitter::complete);
return Mono.fromRunnable(() -> {
if (this.emitter != null) {
this.emitter.complete();
}
});
}
@Override

View File

@@ -1,4 +1,21 @@
package io.shinhanlife.dap.biz.mcp.gateway.sync;
package io.shinhanlife.dap.mcg.sync;
/**
* @package io.shinhanlife.dap.mcg.sync
* @className DynamicMcpController
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import java.util.UUID;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -9,6 +26,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import io.shinhanlife.dap.mcg.sync.CustomWebMvcSseServerTransportProvider;
@RestController
public class DynamicMcpController {
@@ -19,24 +37,6 @@ public class DynamicMcpController {
this.manager = manager;
}
@PostMapping("/mcp")
public SseEmitter handleSse1() {
System.out.println("11111111111");
return null;
}
@PostMapping("/mcp/initialize")
public SseEmitter handleSse2() {
System.out.println("2222222222");
return null;
}
@PostMapping("/mcp/sse/initialize")
public SseEmitter handleSse3() {
System.out.println("33333333333333");
return null;
}
@GetMapping("/mcp/sse/{category}")
public SseEmitter handleSse(@PathVariable("category") String category) {
CustomWebMvcSseServerTransportProvider transport = manager.getTransport(category);
@@ -70,18 +70,18 @@ public class DynamicMcpController {
return ResponseEntity.badRequest().body("Unknown category: " + category);
}
if (sessionId == null || sessionId.isEmpty()) {
// 세션 생성 (initialize 요청)
String newSessionId = java.util.UUID.randomUUID().toString();
SseEmitter emitter = transport.handleCustomSse(newSessionId, body);
return ResponseEntity.ok()
.header("Mcp-Session-Id", newSessionId)
.body(emitter);
} else {
// 기존 세션 메시지 전송 (tools/call )
transport.handleMessage(sessionId, body);
return ResponseEntity.accepted().build();
boolean isNew = (sessionId == null || sessionId.isEmpty());
String activeSessionId = isNew ? java.util.UUID.randomUUID().toString() : sessionId;
if (!isNew && !transport.hasSession(activeSessionId)) {
// 클라이언트가 보낸 세션 ID가 만료되었거나 존재하지 않는 경우 (Warm Pool 스펙: 404 Not Found 반환)
return ResponseEntity.notFound().build();
}
org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter = transport.handleCustomSse(activeSessionId, body);
return ResponseEntity.ok()
.header("Mcp-Session-Id", activeSessionId)
.body(emitter);
}
}

View File

@@ -1,8 +1,25 @@
package io.shinhanlife.dap.biz.mcp.gateway.sync;
package io.shinhanlife.dap.mcg.sync;
/**
* @package io.shinhanlife.dap.mcg.sync
* @className DynamicMcpServerManager
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpSyncServer;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.sync.CustomWebMvcSseServerTransportProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@@ -37,6 +54,7 @@ public class DynamicMcpServerManager {
getOrCreateServer("sms");
getOrCreateServer("email");
getOrCreateServer("other");
getOrCreateServer("sample");
}
private McpSyncServer getOrCreateServer(String categoryKey) {
@@ -50,8 +68,8 @@ public class DynamicMcpServerManager {
CustomWebMvcSseServerTransportProvider transport = new CustomWebMvcSseServerTransportProvider(ssePath, msgPath, objectMapper);
McpSyncServer newServer = McpServer.sync(transport)
.serverInfo("AXHUB-Gateway-" + key, "1.0.0")
.capabilities(io.modelcontextprotocol.spec.McpSchema.ServerCapabilities.builder().tools(true).build())
.serverInfo("DAP-Gateway-" + key, "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
categoryTransports.put(key, transport);

View File

@@ -1,10 +1,25 @@
package io.shinhanlife.dap.biz.mcp.gateway.sync;
package io.shinhanlife.dap.mcg.sync;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.biz.mcp.gateway.service.ExecuteService;
/**
* @package io.shinhanlife.dap.mcg.sync
* @className RegistryMcpToolSpecificationFactory
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.service.ExecuteService;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import io.shinhanlife.dap.biz.mcp.gateway.tool.result.ToolExecutionResult;
import io.shinhanlife.dap.mcg.tool.result.ToolExecutionResult;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -37,6 +52,12 @@ public class RegistryMcpToolSpecificationFactory {
.name(entry.getName())
.description(description(entry))
.inputSchema(inputSchema(entry))
.annotations(McpSchema.ToolAnnotations.builder()
.readOnlyHint(Boolean.TRUE.equals(entry.getReadOnlyHint()))
.destructiveHint(Boolean.TRUE.equals(entry.getDestructiveHint()))
.idempotentHint(Boolean.TRUE.equals(entry.getIdempotentHint()))
.openWorldHint(Boolean.TRUE.equals(entry.getOpenWorldHint()))
.build())
.build();
return McpServerFeatures.SyncToolSpecification.builder()

View File

@@ -1,7 +1,7 @@
package io.shinhanlife.dap.biz.mcp.gateway.sync;
package io.shinhanlife.dap.mcg.sync;
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
import io.shinhanlife.dap.biz.mcp.gateway.registry.RedisRegistryService;
import io.shinhanlife.dap.mcg.dto.ToolMetadata;
import io.shinhanlife.dap.mcg.registry.RedisRegistryService;
import io.modelcontextprotocol.server.McpSyncServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,7 +19,7 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
* @package io.shinhanlife.dap.biz.mcp.gateway.sync
* @package io.shinhanlife.dap.mcg.sync
* @className RegistryMcpToolSynchronizer
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,7 +1,25 @@
package io.shinhanlife.dap.biz.mcp.gateway.tool.large;
package io.shinhanlife.dap.mcg.tool.large;
import io.shinhanlife.dap.biz.mcp.gateway.config.AgentResponseBudgetProperties;
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
/**
* @package io.shinhanlife.dap.mcg.tool.large
* @className AgentResponseBudgetService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import java.util.Iterator;
import java.util.Map;
import io.shinhanlife.dap.mcg.config.AgentResponseBudgetProperties;
import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -99,9 +117,9 @@ public class AgentResponseBudgetService {
int copied = 0;
int originalFields = 0;
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = masked.fields();
Iterator<Map.Entry<String, JsonNode>> fields = masked.fields();
while (fields.hasNext()) {
java.util.Map.Entry<String, JsonNode> entry = fields.next();
Map.Entry<String, JsonNode> entry = fields.next();
originalFields++;
if (copied >= properties.maxFieldsPerItem()) {
continue;
@@ -135,9 +153,9 @@ public class AgentResponseBudgetService {
ObjectNode source = (ObjectNode) item;
ObjectNode limited = json.createObjectNode();
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = source.fields();
Iterator<Map.Entry<String, JsonNode>> fields = source.fields();
while (fields.hasNext()) {
java.util.Map.Entry<String, JsonNode> entry = fields.next();
Map.Entry<String, JsonNode> entry = fields.next();
JsonNode value = entry.getValue();
if (value.isTextual()) {
limited.put(entry.getKey(), truncateText(value.asText()));
@@ -164,7 +182,7 @@ public class AgentResponseBudgetService {
fallback.put("maxItemBytes", properties.maxItemBytes());
if (item.isObject()) {
ArrayNode fieldNames = json.createArrayNode();
java.util.Iterator<String> fieldNamesIter = item.fieldNames();
Iterator<String> fieldNamesIter = item.fieldNames();
while (fieldNamesIter.hasNext()) {
fieldNames.add(fieldNamesIter.next());
}
@@ -188,9 +206,9 @@ public class AgentResponseBudgetService {
private ObjectNode copyWithout(ObjectNode source, String... excludedFields) {
ObjectNode copy = json.createObjectNode();
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = source.fields();
Iterator<Map.Entry<String, JsonNode>> fields = source.fields();
while (fields.hasNext()) {
java.util.Map.Entry<String, JsonNode> entry = fields.next();
Map.Entry<String, JsonNode> entry = fields.next();
if (!excluded(entry.getKey(), excludedFields)) {
copy.set(entry.getKey(), entry.getValue());
}

View File

@@ -1,9 +1,26 @@
package io.shinhanlife.dap.biz.mcp.gateway.tool.large;
package io.shinhanlife.dap.mcg.tool.large;
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
/**
* @package io.shinhanlife.dap.mcg.tool.large
* @className LargeToolResponseService
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import java.util.Iterator;
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker;
import io.shinhanlife.dap.mcg.resilience.FailureType;
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -266,7 +283,7 @@ public class LargeToolResponseService {
preview.put("maxItemBytes", properties.largeResponseMaxItemBytes());
if (masked.isObject()) {
ArrayNode fieldNames = json.createArrayNode();
java.util.Iterator<String> fieldNamesIter = masked.fieldNames();
Iterator<String> fieldNamesIter = masked.fieldNames();
while (fieldNamesIter.hasNext()) {
fieldNames.add(fieldNamesIter.next());
}
@@ -309,7 +326,7 @@ public class LargeToolResponseService {
private int count(JsonNode node) {
int count = 0;
java.util.Iterator<JsonNode> iter = node.elements();
Iterator<JsonNode> iter = node.elements();
while(iter.hasNext()) {
iter.next();
count++;

View File

@@ -1,8 +1,23 @@
package io.shinhanlife.dap.biz.mcp.gateway.tool.large;
package io.shinhanlife.dap.mcg.tool.large;
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
/**
* @package io.shinhanlife.dap.mcg.tool.large
* @className PaginationRequestValidator
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
import io.shinhanlife.dap.mcg.resilience.FailureType;
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

View File

@@ -1,5 +1,20 @@
package io.shinhanlife.dap.biz.mcp.gateway.tool.result;
package io.shinhanlife.dap.mcg.tool.result;
/**
* @package io.shinhanlife.dap.mcg.tool.result
* @className ToolExecutionResult
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;

View File

@@ -1,6 +1,21 @@
package io.shinhanlife.dap.biz.mcp.gateway.tool.result;
package io.shinhanlife.dap.mcg.tool.result;
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
/**
* @package io.shinhanlife.dap.mcg.tool.result
* @className ToolExecutionResultFormatter
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -1,7 +1,22 @@
package io.shinhanlife.dap.biz.mcp.gateway.transport;
package io.shinhanlife.dap.mcg.transport;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
/**
* @package io.shinhanlife.dap.mcg.transport
* @className HttpToolInvoker
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import io.shinhanlife.dap.mcg.resilience.FailureType;
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@@ -25,14 +40,17 @@ public class HttpToolInvoker implements ToolInvoker {
}
@Override
public JsonNode invoke(Map<String, Object> payload, String targetUrl) {
public JsonNode invoke(Map<String, Object> payload, String targetUrl, Map<String, String> headers) {
try {
Object httpResult = restClient.post()
RestClient.RequestBodySpec requestSpec = restClient.post()
.uri(targetUrl)
.contentType(MediaType.APPLICATION_JSON)
// TODO: Use actual tenant's key
.header("X-Trace-Id", UUID.randomUUID().toString())
.body(payload)
.contentType(MediaType.APPLICATION_JSON);
if (headers != null) {
headers.forEach(requestSpec::header);
}
Object httpResult = requestSpec.body(payload)
.retrieve()
.body(Object.class);

View File

@@ -0,0 +1,26 @@
package io.shinhanlife.dap.mcg.transport;
/**
* @package io.shinhanlife.dap.mcg.transport
* @className ToolInvoker
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Map;
/**
* Tool 서버 호출 transport의 최소 공통 인터페이스입니다.
*/
public interface ToolInvoker {
JsonNode invoke(Map<String, Object> payload, String targetUrl, Map<String, String> headers);
}

View File

@@ -1,17 +1,17 @@
# Dev 환경 전용 설정 (개발 서버 DB 연결 정보 등)
# Dev ?瞘祭 ?<3F>鹻 ?木<> (穈嶅<E7A988> ?嶅<> DB ?國盒 ?瑅陷 ??
# spring.datasource.url=jdbc:oracle:thin:@//dev-db-host:1521/XEPDB1
# spring.datasource.driverClassName=oracle.jdbc.OracleDriver
# spring.datasource.username=dev_user
# spring.datasource.password=dev_password
# Render 클라우드 환경 전용 Fallback 라우팅
# Render ?渠𦉘?圉<> ?瞘祭 ?<3F> Fallback ?潰黱??
mcp.gateway.fallback.routes.sms=https://dap-tool-sms.onrender.com
mcp.gateway.fallback.routes.email=https://dap-tool-email.onrender.com
mcp.gateway.fallback.default-url=https://dap-tool-other.onrender.com
mcp.gateway.fallback.routes.payment=https://dap-tool-payment.onrender.com
mcp.gateway.fallback.routes.hr=https://dap-tool-hr.onrender.com
# --- 신한라이프 EAI/MCI 연계 IP 정보 (개발 환경) ---
# --- ?𡥄<>?潰𦚯??EAI/MCI ?國<> IP ?瑅陷 (穈嶅<E7A988> ?瞘祭) ---
shinhan.integration.envrTypeCd=D
shinhan.integration.eai.url=http://10.176.32.181
shinhan.integration.internalMci.url=http://10.176.32.173

View File

@@ -43,6 +43,7 @@ mcp.tool-registry-sync-interval-millis=5000
# API 보안 키 설정
mcp.security.tenant-domains.TESTER-DEV=ALL
mcp.security.tenant-domains.system=ALL
# Spring AI OpenAI / Gemini API Config
spring.ai.openai.api-key=AQ.Ab8RN6KFZggsQf8iooY1v_3h3vp2TIjiYB54dV4Yay3vVKEMtg
spring.ai.openai.base-url=https://generativelanguage.googleapis.com/v1beta/openai/

View File

@@ -13,10 +13,10 @@
<!-- 3. 파일(File) 출력 설정 (서버 운영용) -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/axhub-gateway.log</file> <!-- 현재 로그가 쌓이는 파일 -->
<file>logs/dap-gateway.log</file> <!-- 현재 로그가 쌓이는 파일 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 매일 자정에 날짜를 붙여서 지난 로그를 분리 저장 -->
<fileNamePattern>logs/axhub-gateway-%d{yyyy-MM-dd}.log</fileNamePattern>
<fileNamePattern>logs/dap-gateway-%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 최대 30일치 로그만 보관하고 오래된 것은 자동 삭제 -->
<maxHistory>30</maxHistory>
</rollingPolicy>

View File

@@ -464,6 +464,7 @@
<label class="form-label">Domain Category</label>
<input type="text" class="form-control" name="categoryKey" pattern="^[a-z0-9][a-z0-9_-]*$" list="groupList" placeholder="e.g. common, hr, payment" oninput="this.value = this.value.toLowerCase()" required>
<datalist id="groupList">
<option value="sample">sample</option>
<option value="common">common</option>
<option value="customer">customer</option>
<option value="contract">contract</option>

View File

@@ -1,11 +1,26 @@
package io.shinhanlife.dap.biz.mcp.gateway;
package io.shinhanlife.dap.mcg;
/**
* @package io.shinhanlife.dap.mcg
* @className DapGatewayApplicationTests
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import io.shinhanlife.dap.biz.mcp.gateway.audit.AuditLogService;
import io.shinhanlife.dap.biz.mcp.gateway.redis.RedisToolTraceService;
import io.shinhanlife.dap.mcg.audit.AuditLogService;
import io.shinhanlife.dap.mcg.redis.RedisToolTraceService;
@SpringBootTest
@ActiveProfiles("test")

View File

@@ -1,9 +1,24 @@
package io.shinhanlife.dap.biz.mcp.gateway.tool.large;
package io.shinhanlife.dap.mcg.tool.large;
/**
* @package io.shinhanlife.dap.mcg.tool.large
* @className PaginationRequestValidatorTest
* @description AX HUB 시스템 처리 클래스
* @author 김형식
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 김형식 최초생성
*
* </pre>
*/
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
import io.shinhanlife.dap.mcg.config.McpGatewayProperties;
import io.shinhanlife.dap.mcg.resilience.ToolExecutionException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@@ -3,7 +3,30 @@ plugins {
}
dependencies {
api project(':dap-common')
api 'org.springframework.boot:spring-boot-starter-web'
api 'org.springframework.boot:spring-boot-starter-validation'
api 'org.springframework.boot:spring-boot-starter-data-redis'
api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
api 'io.github.resilience4j:resilience4j-core:2.2.0'
api 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
api 'io.github.resilience4j:resilience4j-ratelimiter'
api 'io.github.resilience4j:resilience4j-retry:2.2.0'
api 'org.springframework.boot:spring-boot-starter-aop:3.3.0'
api 'org.springframework.boot:spring-boot-starter-jdbc'
api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
api 'com.h2database:h2'
api 'p6spy:p6spy:3.9.1'
api 'io.lettuce:lettuce-core:6.6.0.RELEASE'
api "org.mapstruct:mapstruct:1.5.5.Final"
api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1'
api 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
api 'com.networknt:json-schema-validator:1.4.0'
api 'org.springframework.kafka:spring-kafka:3.2.0'
api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
}
dependencies {

View File

@@ -1,11 +0,0 @@
package io.shinhanlife.dap.biz.mcp.tool.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface McpParameter {
String description();
boolean required() default false;
}

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.dap.biz.mcp.adapter.aop;
package io.shinhanlife.dap.common.adapter.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @package io.shinhanlife.dap.biz.mcp.adapter.aop
* @package io.shinhanlife.dap.common.adapter.aop
* @className EimsMonitoringAspect
* @description AX HUB 시스템 처리 클래스
* @author 김형식
@@ -27,8 +27,8 @@ import java.util.Arrays;
@Component
public class EimsMonitoringAspect {
// 포인트컷: io.shinhanlife.dap.biz.mcp.adapter.sender 패키지 내의 EimsSender를 구현한 모든 클래스의 메서드를 타겟으로 지정합니다.
@Around("execution(* io.shinhanlife.dap.biz.mcp.adapter.sender.*EimsSender.*(..))")
// 포인트컷: io.shinhanlife.dap.common.adapter.sender 패키지 내의 EimsSender를 구현한 모든 클래스의 메서드를 타겟으로 지정합니다.
@Around("execution(* io.shinhanlife.dap.common.adapter.sender.*EimsSender.*(..))")
public Object monitorEimsCommunication(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 호출되는 클래스와 메서드 이름 추출

View File

@@ -1,7 +1,7 @@
package io.shinhanlife.dap.biz.mcp.adapter.connector;
package io.shinhanlife.dap.common.adapter.connector;
import io.shinhanlife.dap.biz.mcp.adapter.support.DynamicPayloadBuilder;
import io.shinhanlife.dap.biz.mcp.adapter.support.DynamicSchemaValidator;
import io.shinhanlife.dap.common.adapter.support.DynamicPayloadBuilder;
import io.shinhanlife.dap.common.adapter.support.DynamicSchemaValidator;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import lombok.extern.slf4j.Slf4j;
@@ -12,7 +12,7 @@ import java.util.Map;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.mcp.adapter.connector
* @package io.shinhanlife.dap.common.adapter.connector
* @className ExternalApiConnector
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,7 +1,7 @@
package io.shinhanlife.dap.biz.mcp.adapter.connector;
package io.shinhanlife.dap.common.adapter.connector;
import io.shinhanlife.dap.biz.mcp.adapter.support.DynamicPayloadBuilder;
import io.shinhanlife.dap.biz.mcp.adapter.support.DynamicSchemaValidator;
import io.shinhanlife.dap.common.adapter.support.DynamicPayloadBuilder;
import io.shinhanlife.dap.common.adapter.support.DynamicSchemaValidator;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import lombok.extern.slf4j.Slf4j;
@@ -12,7 +12,7 @@ import java.util.Map;
import java.util.List;
/**
* @package io.shinhanlife.dap.biz.mcp.adapter.connector
* @package io.shinhanlife.dap.common.adapter.connector
* @className InternalSystemConnector
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,6 +1,6 @@
package io.shinhanlife.dap.biz.mcp.adapter.connector;
package io.shinhanlife.dap.common.adapter.connector;
import io.shinhanlife.dap.biz.mcp.adapter.support.ResultStandardizer;
import io.shinhanlife.dap.common.adapter.support.ResultStandardizer;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import lombok.RequiredArgsConstructor;
@@ -13,7 +13,7 @@ import java.util.Map;
import java.util.stream.Collectors;
/**
* @package io.shinhanlife.dap.biz.mcp.adapter.connector
* @package io.shinhanlife.dap.common.adapter.connector
* @className LegacyDbConnector
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,7 +1,7 @@
package io.shinhanlife.dap.biz.mcp.adapter.connector;
package io.shinhanlife.dap.common.adapter.connector;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.shinhanlife.dap.biz.mcp.adapter.sender.EimsSender;
import io.shinhanlife.dap.common.adapter.sender.EimsSender;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
@@ -12,10 +12,10 @@ import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import io.shinhanlife.dap.biz.mcp.adapter.util.LegacyDataTransformer;
import io.shinhanlife.dap.common.adapter.util.LegacyDataTransformer;
/**
* @package io.shinhanlife.dap.biz.mcp.adapter.connector
* @package io.shinhanlife.dap.common.adapter.connector
* @className LegacyEimsConnector
* @description AX HUB 시스템 처리 클래스
* @author 김형식

View File

@@ -1,4 +1,4 @@
package io.shinhanlife.dap.biz.mcp.adapter.connector;
package io.shinhanlife.dap.common.adapter.connector;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
@@ -8,7 +8,7 @@ import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @package io.shinhanlife.dap.biz.mcp.adapter.connector
* @package io.shinhanlife.dap.common.adapter.connector
* @className ThirdPartySecurityConnector
* @description AX HUB 시스템 처리 클래스
* @author 김형식

Some files were not shown because too many files have changed in this diff Show More