diff --git a/potal/agent-test-backend/.classpath b/potal/agent-test-backend/.classpath
deleted file mode 100644
index be88c88..0000000
--- a/potal/agent-test-backend/.classpath
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/potal/agent-test-backend/.project b/potal/agent-test-backend/.project
deleted file mode 100644
index f5751fa..0000000
--- a/potal/agent-test-backend/.project
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
- agent-test-backend
- Project agent-test-backend created by Buildship.
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.buildship.core.gradleprojectbuilder
-
-
-
-
- org.springframework.ide.eclipse.boot.validation.springbootbuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
- org.eclipse.buildship.core.gradleprojectnature
-
-
diff --git a/potal/agent-test-backend/.settings/org.eclipse.buildship.core.prefs b/potal/agent-test-backend/.settings/org.eclipse.buildship.core.prefs
deleted file mode 100644
index e479558..0000000
--- a/potal/agent-test-backend/.settings/org.eclipse.buildship.core.prefs
+++ /dev/null
@@ -1,13 +0,0 @@
-arguments=
-auto.sync=false
-build.scans.enabled=false
-connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
-connection.project.dir=
-eclipse.preferences.version=1
-gradle.user.home=
-java.home=
-jvm.arguments=
-offline.mode=false
-override.workspace.settings=false
-show.console.view=false
-show.executions.view=false
diff --git a/potal/agent-test-backend/.settings/org.eclipse.jdt.core.prefs b/potal/agent-test-backend/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index e9186c3..0000000
--- a/potal/agent-test-backend/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
-org.eclipse.jdt.core.compiler.compliance=21
-org.eclipse.jdt.core.compiler.source=21
diff --git a/potal/agent-test-backend/.settings/org.springframework.ide.eclipse.prefs b/potal/agent-test-backend/.settings/org.springframework.ide.eclipse.prefs
deleted file mode 100644
index a12794d..0000000
--- a/potal/agent-test-backend/.settings/org.springframework.ide.eclipse.prefs
+++ /dev/null
@@ -1,2 +0,0 @@
-boot.validation.initialized=true
-eclipse.preferences.version=1
diff --git a/potal/agent-test-backend/README.md b/potal/agent-test-backend/README.md
deleted file mode 100644
index b1875f0..0000000
--- a/potal/agent-test-backend/README.md
+++ /dev/null
@@ -1,137 +0,0 @@
-# Agent Test Backend / Portal PoC
-
-Browser-based PoC tester and hardcoded Portal Registry screen for:
-
-```text
-Browser -> Agent Test Backend -> MCP Server -> Tool Server
-```
-
-## Ports
-
-```text
-Tool Server http://localhost:9092
-MCP Server http://localhost:8080/mcp
-Agent Test Backend http://localhost:7070
-```
-
-## STS Run
-
-Import this folder as an existing Gradle project:
-
-```text
-C:\Users\hyo\Documents\Codex\agent-test-backend
-```
-
-Run `com.example.agenttest.AgentTestBackendApplication`.
-
-Default environment:
-
-```text
-AGENT_TEST_PORT=7070
-MCP_ENDPOINT_URL=http://localhost:8080/mcp
-MCP_PROTOCOL_VERSION=2025-11-25
-TOOL_MANIFEST_URL=http://localhost:9092/tool-manifest
-TOOL_SERVER_API_KEY=tool-server-key
-PORTAL_REGISTRY_REVISION=1
-PORTAL_ROUTE_KEY=external
-TOOL_SERVICE_DOMAIN=http://localhost:9092
-```
-
-Open:
-
-```text
-http://localhost:7070
-```
-
-## Recommended Verification Order
-
-1. Start Tool Server in STS.
-2. Start MCP Server with Tool Server environment values.
-3. Start Agent Test Backend.
-4. Open `http://localhost:7070`.
-5. Click `Initialize`, `Tools/List`, then run `Agent Chat` or `Tools/Call`.
-
-## Portal PoC Scope
-
-The current screen intentionally uses hardcoded registry data instead of DB tables:
-
-```text
-MCP Route external -> http://localhost:8080/mcp
-Tool Service external-tools -> http://localhost:9092/tool-manifest
-Mapping external -> external-tools
-```
-
-The MCP-facing Portal Registry API is:
-
-```text
-GET http://localhost:7070/api/portal/registry/external
-```
-
-For this PoC, MCP can use these IntelliJ environment variables after applying
-`C:\Users\hyo\Documents\Codex\mcp-portal-registry.patch` to the MCP project:
-
-```text
-MCP_DISCOVERY_ENABLED=true
-MCP_PORTAL_ENABLED=true
-MCP_PORTAL_ROUTE_KEY=external
-MCP_PORTAL_REGISTRY_URL=http://localhost:7070/api/portal/registry/external
-MCP_REGISTRY_REFRESH_INTERVAL_SECONDS=60
-```
-
-`POST /api/agent/chat` is a first Agent Backend skeleton. It currently uses a simple rule-based
-planner so it can run without an OpenAI API key:
-
-```text
-"서울 날씨 알려줘" -> external.weather_lookup
-"달러 환율 알려줘" -> external.exchange_rate
-```
-
-Later, replace the rule-based planner with Codex/OpenAI model selection while keeping the same MCP
-`tools/list` and `tools/call` boundary.
-
-## Portal Tool Bundle Management
-
-The Portal now manages both seeded pull-discovery bundles through one API and UI flow:
-
-```text
-external-tools -> http://localhost:9092/tool-manifest
-business-tools -> http://localhost:9090/tool-manifest
-```
-
-Bundle APIs:
-
-```text
-GET /api/portal/bundles
-POST /api/portal/bundles
-PUT /api/portal/bundles/{bundleId}
-POST /api/portal/bundles/{bundleId}/sync
-```
-
-The API key is write-only. Read responses expose only `apiKeyConfigured`, and logs never contain
-the key value. Manifest synchronization validates the configured Bundle ID, Tool name prefix,
-required metadata objects, and the configured execution endpoint for every mapped Tool.
-
-Business Tool endpoint mappings are owned by the Portal/MCP configuration rather than the
-manifest:
-
-```text
-business.customer_search -> POST http://localhost:9090/internal/tools/customer-search
-business.order_status -> POST http://localhost:9090/internal/tools/order-status
-business.ticket_create -> POST http://localhost:9090/internal/tools/ticket-create
-```
-
-The Bundle definitions and synchronized snapshots are currently held in memory because this PoC
-does not include a database dependency or an existing schema/migration framework. Therefore no DB
-migration is required. Restarting the Portal restores the two seeded definitions and clears cached
-manifest snapshots. For persistent operation, the `PortalBundleService` store is the boundary to
-replace with the project's chosen repository and migration framework.
-
-Start the business Tool server with pull discovery only:
-
-```text
-TOOL_AUTO_REGISTER_ENABLED=false
-TOOL_HEARTBEAT_ENABLED=false
-TOOL_SERVER_PORT=9090
-```
-
-After starting the Tool server, open the Portal and click `Sync Manifest` for `business-tools`.
diff --git a/potal/agent-test-backend/bin/main/application.yml b/potal/agent-test-backend/bin/main/application.yml
deleted file mode 100644
index 7c5fa8f..0000000
--- a/potal/agent-test-backend/bin/main/application.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-server:
- port: ${AGENT_TEST_PORT:7070}
-
-agent-test:
- mcp:
- endpoint-url: ${MCP_ENDPOINT_URL:http://localhost:8080/mcp}
- protocol-version: ${MCP_PROTOCOL_VERSION:2025-11-25}
- tool-server:
- manifest-url: ${TOOL_MANIFEST_URL:http://localhost:9092/tool-manifest}
- api-key: ${TOOL_SERVER_API_KEY:tool-server-key}
- portal:
- registry-revision: ${PORTAL_REGISTRY_REVISION:1}
- route-key: ${PORTAL_ROUTE_KEY:cus}
- tool-service-domain: ${TOOL_SERVICE_DOMAIN:http://localhost:9092}
-
-logging:
- level:
- com.example.agenttest: INFO
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestBackendApplication.class b/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestBackendApplication.class
deleted file mode 100644
index a2f5fb4..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestBackendApplication.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$Mcp.class b/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$Mcp.class
deleted file mode 100644
index 6e2e70e..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$Mcp.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$Portal.class b/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$Portal.class
deleted file mode 100644
index dce893b..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$Portal.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$ToolServer.class b/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$ToolServer.class
deleted file mode 100644
index 41cc1e6..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties$ToolServer.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties.class b/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties.class
deleted file mode 100644
index 52f942f..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/AgentTestProperties.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$ChatRequest.class b/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$ChatRequest.class
deleted file mode 100644
index 8d85bb8..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$ChatRequest.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$PlannedTool.class b/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$PlannedTool.class
deleted file mode 100644
index 20f3ce4..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$PlannedTool.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$ToolCallRequest.class b/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$ToolCallRequest.class
deleted file mode 100644
index bce47ce..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController$ToolCallRequest.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController.class b/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController.class
deleted file mode 100644
index cfb93d8..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/McpProxyController.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalApiExceptionHandler.class b/potal/agent-test-backend/bin/main/com/example/agenttest/PortalApiExceptionHandler.class
deleted file mode 100644
index 97ad0ff..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalApiExceptionHandler.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleController.class b/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleController.class
deleted file mode 100644
index 4a761b6..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleController.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleDefinition.class b/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleDefinition.class
deleted file mode 100644
index 21af855..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleDefinition.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundlePublicDefinition.class b/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundlePublicDefinition.class
deleted file mode 100644
index 80d433f..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundlePublicDefinition.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleRequest.class b/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleRequest.class
deleted file mode 100644
index acb032c..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleRequest.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleState.class b/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleState.class
deleted file mode 100644
index 4d28d0b..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleState.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleView.class b/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleView.class
deleted file mode 100644
index 55fe735..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService$BundleView.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService.class b/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService.class
deleted file mode 100644
index c9abcd7..0000000
Binary files a/potal/agent-test-backend/bin/main/com/example/agenttest/PortalBundleService.class and /dev/null differ
diff --git a/potal/agent-test-backend/bin/main/static/index.html b/potal/agent-test-backend/bin/main/static/index.html
deleted file mode 100644
index f47ca89..0000000
--- a/potal/agent-test-backend/bin/main/static/index.html
+++ /dev/null
@@ -1,625 +0,0 @@
-
-
-
-
-
- AX HUB Portal PoC
-
-
-
-
-
-
AX HUB Portal PoC
-
-
-
-
-
-
-
-
- Portal Registry
-
-
-
-
-
-
-
- Tool Server 설정
-
-
-
-
-
-
- MCP 연결 테스트
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Agent 테스트
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Agent 응답 대기 중
-
-
-
-
-
- Tool 직접 호출
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 상세 응답 보기
- Waiting...
-
-
-
-
-
-
-
-
diff --git a/potal/agent-test-backend/build.gradle b/potal/agent-test-backend/build.gradle
deleted file mode 100644
index b0177cc..0000000
--- a/potal/agent-test-backend/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-plugins {
- id 'java'
- id 'org.springframework.boot' version '3.5.11'
- id 'io.spring.dependency-management' version '1.1.7'
-}
-
-group = 'com.example'
-version = '0.1.0'
-description = 'agent-test-backend'
-
-java {
- toolchain {
- languageVersion = JavaLanguageVersion.of(21)
- }
-}
-
-dependencies {
- implementation 'org.springframework.boot:spring-boot-starter-web'
- implementation 'org.springframework.boot:spring-boot-starter-validation'
-
- testImplementation 'org.springframework.boot:spring-boot-starter-test'
- testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
-}
-
-tasks.named('test') {
- useJUnitPlatform()
-}
diff --git a/potal/agent-test-backend/gradle/wrapper/gradle-wrapper.jar b/potal/agent-test-backend/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 1b33c55..0000000
Binary files a/potal/agent-test-backend/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/potal/agent-test-backend/gradle/wrapper/gradle-wrapper.properties b/potal/agent-test-backend/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index d4081da..0000000
--- a/potal/agent-test-backend/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,7 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
-networkTimeout=10000
-validateDistributionUrl=true
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/potal/agent-test-backend/gradlew b/potal/agent-test-backend/gradlew
deleted file mode 100644
index 0f14772..0000000
--- a/potal/agent-test-backend/gradlew
+++ /dev/null
@@ -1,251 +0,0 @@
-#!/bin/sh
-
-#
-# Copyright © 2015-2021 the original authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# SPDX-License-Identifier: Apache-2.0
-#
-
-##############################################################################
-#
-# Gradle start up script for POSIX generated by Gradle.
-#
-# Important for running:
-#
-# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
-# noncompliant, but you have some other compliant shell such as ksh or
-# bash, then to run this script, type that shell name before the whole
-# command line, like:
-#
-# ksh Gradle
-#
-# Busybox and similar reduced shells will NOT work, because this script
-# requires all of these POSIX shell features:
-# * functions;
-# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
-# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
-# * compound commands having a testable exit status, especially «case»;
-# * various built-in commands including «command», «set», and «ulimit».
-#
-# Important for patching:
-#
-# (2) This script targets any POSIX shell, so it avoids extensions provided
-# by Bash, Ksh, etc; in particular arrays are avoided.
-#
-# The "traditional" practice of packing multiple parameters into a
-# space-separated string is a well documented source of bugs and security
-# problems, so this is (mostly) avoided, by progressively accumulating
-# options in "$@", and eventually passing that to Java.
-#
-# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
-# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
-# see the in-line comments for details.
-#
-# There are tweaks for specific operating systems such as AIX, CygWin,
-# Darwin, MinGW, and NonStop.
-#
-# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
-# within the Gradle project.
-#
-# You can find Gradle at https://github.com/gradle/gradle/.
-#
-##############################################################################
-
-# Attempt to set APP_HOME
-
-# Resolve links: $0 may be a link
-app_path=$0
-
-# Need this for daisy-chained symlinks.
-while
- APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
- [ -h "$app_path" ]
-do
- ls=$( ls -ld "$app_path" )
- link=${ls#*' -> '}
- case $link in #(
- /*) app_path=$link ;; #(
- *) app_path=$APP_HOME$link ;;
- esac
-done
-
-# This is normally unused
-# shellcheck disable=SC2034
-APP_BASE_NAME=${0##*/}
-# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD=maximum
-
-warn () {
- echo "$*"
-} >&2
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-} >&2
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "$( uname )" in #(
- CYGWIN* ) cygwin=true ;; #(
- Darwin* ) darwin=true ;; #(
- MSYS* | MINGW* ) msys=true ;; #(
- NONSTOP* ) nonstop=true ;;
-esac
-
-CLASSPATH="\\\"\\\""
-
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD=$JAVA_HOME/jre/sh/java
- else
- JAVACMD=$JAVA_HOME/bin/java
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD=java
- if ! command -v java >/dev/null 2>&1
- then
- die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-fi
-
-# Increase the maximum file descriptors if we can.
-if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
- case $MAX_FD in #(
- max*)
- # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC2039,SC3045
- MAX_FD=$( ulimit -H -n ) ||
- warn "Could not query maximum file descriptor limit"
- esac
- case $MAX_FD in #(
- '' | soft) :;; #(
- *)
- # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC2039,SC3045
- ulimit -n "$MAX_FD" ||
- warn "Could not set maximum file descriptor limit to $MAX_FD"
- esac
-fi
-
-# Collect all arguments for the java command, stacking in reverse order:
-# * args from the command line
-# * the main class name
-# * -classpath
-# * -D...appname settings
-# * --module-path (only if needed)
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if "$cygwin" || "$msys" ; then
- APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
- CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
-
- JAVACMD=$( cygpath --unix "$JAVACMD" )
-
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- for arg do
- if
- case $arg in #(
- -*) false ;; # don't mess with options #(
- /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
- [ -e "$t" ] ;; #(
- *) false ;;
- esac
- then
- arg=$( cygpath --path --ignore --mixed "$arg" )
- fi
- # Roll the args list around exactly as many times as the number of
- # args, so each arg winds up back in the position where it started, but
- # possibly modified.
- #
- # NB: a `for` loop captures its iteration list before it begins, so
- # changing the positional parameters here affects neither the number of
- # iterations, nor the values presented in `arg`.
- shift # remove old arg
- set -- "$@" "$arg" # push replacement arg
- done
-fi
-
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"'
-
-# Collect all arguments for the java command:
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
-# and any embedded shellness will be escaped.
-# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
-# treated as '${Hostname}' itself on the command line.
-
-set -- \
- "-Dorg.gradle.appname=$APP_BASE_NAME" \
- -classpath "$CLASSPATH" \
- -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
- "$@"
-
-# Stop when "xargs" is not available.
-if ! command -v xargs >/dev/null 2>&1
-then
- die "xargs is not available"
-fi
-
-# Use "xargs" to parse quoted args.
-#
-# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
-#
-# In Bash we could simply go:
-#
-# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
-# set -- "${ARGS[@]}" "$@"
-#
-# but POSIX shell has neither arrays nor command substitution, so instead we
-# post-process each arg (as a line of input to sed) to backslash-escape any
-# character that might be a shell metacharacter, then use eval to reverse
-# that process (while maintaining the separation between arguments), and wrap
-# the whole thing up as a single "set" statement.
-#
-# This will of course break if any of these variables contains a newline or
-# an unmatched quote.
-#
-
-eval "set -- $(
- printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
- xargs -n1 |
- sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
- tr '\n' ' '
- )" '"$@"'
-
-exec "$JAVACMD" "$@"
diff --git a/potal/agent-test-backend/gradlew.bat b/potal/agent-test-backend/gradlew.bat
deleted file mode 100644
index 8de1053..0000000
--- a/potal/agent-test-backend/gradlew.bat
+++ /dev/null
@@ -1,94 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-@rem SPDX-License-Identifier: Apache-2.0
-@rem
-
-@if "%DEBUG%"=="" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%"=="" set DIRNAME=.
-@rem This is normally unused
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if %ERRORLEVEL% equ 0 goto execute
-
-echo. 1>&2
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo. 1>&2
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/potal/agent-test-backend/settings.gradle b/potal/agent-test-backend/settings.gradle
deleted file mode 100644
index 9576414..0000000
--- a/potal/agent-test-backend/settings.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-pluginManagement {
- repositories {
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
-dependencyResolutionManagement {
- repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
- repositories {
- mavenCentral()
- }
-}
-
-rootProject.name = 'agent-test-backend'
diff --git a/potal/agent-test-backend/src/main/java/com/example/agenttest/AgentTestBackendApplication.java b/potal/agent-test-backend/src/main/java/com/example/agenttest/AgentTestBackendApplication.java
deleted file mode 100644
index 9f126c0..0000000
--- a/potal/agent-test-backend/src/main/java/com/example/agenttest/AgentTestBackendApplication.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.example.agenttest;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
-import org.springframework.scheduling.annotation.EnableScheduling;
-
-@SpringBootApplication
-@ConfigurationPropertiesScan
-@EnableScheduling
-public class AgentTestBackendApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(AgentTestBackendApplication.class, args);
- }
-}
diff --git a/potal/agent-test-backend/src/main/java/com/example/agenttest/AgentTestProperties.java b/potal/agent-test-backend/src/main/java/com/example/agenttest/AgentTestProperties.java
deleted file mode 100644
index 6785715..0000000
--- a/potal/agent-test-backend/src/main/java/com/example/agenttest/AgentTestProperties.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.example.agenttest;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@ConfigurationProperties(prefix = "agent-test")
-public record AgentTestProperties(Mcp mcp, ToolServer toolServer, Portal portal) {
-
- public record Mcp(String endpointUrl, String protocolVersion) {
- }
-
- public record ToolServer(String manifestUrl, String apiKey) {
- }
-
- public record Portal(long registryRevision, String routeKey, String toolServiceDomain) {
- }
-}
diff --git a/potal/agent-test-backend/src/main/java/com/example/agenttest/McpProxyController.java b/potal/agent-test-backend/src/main/java/com/example/agenttest/McpProxyController.java
deleted file mode 100644
index f12f1bb..0000000
--- a/potal/agent-test-backend/src/main/java/com/example/agenttest/McpProxyController.java
+++ /dev/null
@@ -1,476 +0,0 @@
-package com.example.agenttest;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import jakarta.servlet.http.HttpServletRequest;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.time.Year;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-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 org.springframework.web.client.RestClient;
-
-@RestController
-@RequestMapping("/api")
-public class McpProxyController {
-
- private static final Logger log = LoggerFactory.getLogger(McpProxyController.class);
- private static final String MCP_SESSION_ID_HEADER = "Mcp-Session-Id";
- private static final String MCP_PROTOCOL_VERSION_HEADER = "MCP-Protocol-Version";
- private static final String TOOL_SERVER_API_KEY_HEADER = "X-Tool-Server-API-Key";
-
- private final AgentTestProperties properties;
- private final ObjectMapper objectMapper;
- private final RestClient restClient;
- private final AtomicLong ids = new AtomicLong(1);
- private final PortalBundleService portalBundles;
- private final AtomicReference latestSessionId = new AtomicReference<>();
-
- public McpProxyController(
- AgentTestProperties properties,
- ObjectMapper objectMapper,
- RestClient.Builder builder,
- PortalBundleService portalBundles) {
- this.properties = properties;
- this.objectMapper = objectMapper;
- this.restClient = builder.build();
- this.portalBundles = portalBundles;
- }
-
- @GetMapping("/config")
- public Map config() {
- Map config = new LinkedHashMap<>();
- config.put("mcpEndpointUrl", properties.mcp().endpointUrl());
- config.put("mcpProtocolVersion", properties.mcp().protocolVersion());
- config.put("toolManifestUrl", properties.toolServer().manifestUrl());
- config.put("defaultRouteKey", defaultRouteKey());
- config.put("defaultRoutedMcpEndpointUrl", mcpEndpointUrl(defaultRouteKey()));
- config.put("latestSessionId", latestSessionId.get());
- return config;
- }
-
- @GetMapping("/registry")
- public Map registry() {
- String routeKey = defaultRouteKey();
- return portalBundles.screenRegistry(routeKey, mcpEndpointUrl(routeKey));
- }
-
- @GetMapping("/portal/registry")
- public Map portalRegistry(HttpServletRequest request) {
- log.info("Inbound portal aggregate registry request: method={}, uri={}, remoteAddress={}, userAgent={}, accept={}",
- request.getMethod(), request.getRequestURI(), request.getRemoteAddr(),
- request.getHeader("User-Agent"), request.getHeader("Accept"));
-
- Map registry = portalBundles.portalRegistry();
- log.info("Portal aggregate registry response: registryRevision={}, routes={}",
- registry.get("registryRevision"), registry.get("routes"));
- return registry;
- }
-
- @GetMapping("/portal/registry/{routeKey}")
- public Map portalRegistry(
- @PathVariable("routeKey") String routeKey,
- HttpServletRequest request) {
- log.info("Inbound portal registry request: method={}, uri={}, remoteAddress={}, userAgent={}, accept={}",
- request.getMethod(), request.getRequestURI(), request.getRemoteAddr(),
- request.getHeader("User-Agent"), request.getHeader("Accept"));
-
- Map registry = portalBundles.portalRegistry(routeKey);
- log.info("Portal registry response: routeKey={}, registryRevision={}, toolServices={}",
- routeKey, registry.get("registryRevision"), registry.get("toolServices"));
- return registry;
- }
-
- @PostMapping("/portal/registry/{routeKey}/revision")
- public Map bumpPortalRegistryRevision(@PathVariable("routeKey") String routeKey) {
- long nextRevision = portalBundles.bumpRevision();
- log.info("Portal registry revision changed: routeKey={}, registryRevision={}", routeKey, nextRevision);
- return Map.of(
- "routeKey", routeKey,
- "registryRevision", nextRevision);
- }
-
- @GetMapping("/tool-manifest")
- public Map toolManifest() {
- log.info("Outbound tool-server request: method=GET, uri={}, headers={{{}={}}}",
- properties.toolServer().manifestUrl(), TOOL_SERVER_API_KEY_HEADER, masked());
- ResponseEntity response = restClient.get()
- .uri(properties.toolServer().manifestUrl())
- .header(TOOL_SERVER_API_KEY_HEADER, properties.toolServer().apiKey())
- .retrieve()
- .toEntity(JsonNode.class);
- log.info("Inbound tool-server response: status={}, body={}",
- response.getStatusCode().value(), response.getBody());
- return response("tool-manifest", response);
- }
-
- @PostMapping("/mcp/initialize")
- public Map initialize() {
- return initialize(properties.portal().routeKey());
- }
-
- @PostMapping("/mcp/{routeKey}/initialize")
- public Map initialize(@PathVariable("routeKey") String routeKey) {
- Map params = Map.of(
- "protocolVersion", properties.mcp().protocolVersion(),
- "capabilities", Map.of(),
- "clientInfo", Map.of(
- "name", "agent-test-backend",
- "version", "0.1.0"));
- ResponseEntity response = postMcp(routeKey, jsonRpc("initialize", params), false);
- String sessionId = response.getHeaders().getFirst(MCP_SESSION_ID_HEADER);
- if (sessionId != null && !sessionId.isBlank()) {
- latestSessionId.set(sessionId);
- }
- return response("initialize", response);
- }
-
- @PostMapping("/mcp/initialized")
- public Map initialized() {
- return initialized(properties.portal().routeKey());
- }
-
- @PostMapping("/mcp/{routeKey}/initialized")
- public Map initialized(@PathVariable("routeKey") String routeKey) {
- ResponseEntity response = postMcp(routeKey, notification("notifications/initialized"), true);
- return response("notifications/initialized", response);
- }
-
- @PostMapping("/mcp/tools/list")
- public Map toolsList() {
- return toolsList(properties.portal().routeKey());
- }
-
- @PostMapping("/mcp/{routeKey}/tools/list")
- public Map toolsList(@PathVariable("routeKey") String routeKey) {
- ResponseEntity response = postMcp(routeKey, jsonRpc("tools/list", Map.of()), true);
- return response("tools/list", response);
- }
-
- @PostMapping("/mcp/tools/call")
- public Map toolsCall(@RequestBody ToolCallRequest request) {
- return callTool(routeKeyForTool(request.name()), request.name(), request.arguments() == null ? Map.of() : request.arguments());
- }
-
- @PostMapping("/mcp/{routeKey}/tools/call")
- public Map toolsCall(
- @PathVariable("routeKey") String routeKey,
- @RequestBody ToolCallRequest request) {
- return callTool(routeKey, request.name(), request.arguments() == null ? Map.of() : request.arguments());
- }
-
- @PostMapping("/agent/chat")
- public Map chat(@RequestBody ChatRequest request) {
- PlannedTool plannedTool = plan(request.message());
- String routeKey = plannedTool.routeKey();
- Map toolCall = callTool(routeKey, plannedTool.name(), plannedTool.arguments());
- JsonNode body = objectMapper.valueToTree(toolCall.get("body"));
- JsonNode toolPayload = firstToolText(body);
- if (body.path("result").path("isError").asBoolean(false)) {
- String toolError = toolPayload.path("text").asText("도구 호출에 실패했습니다.");
- toolPayload = objectMapper.createObjectNode().put("error", toolError);
- }
-
- Map result = new LinkedHashMap<>();
- result.put("routeKey", routeKey);
- result.put("mcpEndpointUrl", mcpEndpointUrl(routeKey));
- result.put("message", request.message());
- result.put("selectedTool", plannedTool.name());
- result.put("routeDecision", "%s prefix Tool은 %s route로 전송".formatted(
- toolPrefix(plannedTool.name()), routeKey));
- result.put("arguments", plannedTool.arguments());
- result.put("answer", answer(plannedTool.name(), toolPayload));
- result.put("toolResult", toolPayload);
- result.put("rawMcpResponse", toolCall);
- return result;
- }
-
- private Map callTool(String routeKey, String name, Map arguments) {
- Map params = Map.of("name", name, "arguments", arguments);
- ResponseEntity response = postMcp(routeKey, jsonRpc("tools/call", params), true);
- return response("tools/call", response);
- }
-
- private PlannedTool plan(String message) {
- return planRequest(message);
- }
-
- static PlannedTool planRequest(String message) {
- String normalized = message == null ? "" : message.toLowerCase(Locale.ROOT);
- if (normalized.contains("메타 공통코드")) {
- return new PlannedTool("cus", "cmm_comcode_lookup",
- Map.of("groupCode", "GRP_COMM_CD", "useYn", "Y"));
- }
- if (normalized.contains("메타 테이블")) {
- return new PlannedTool("cus", "cmm_meta_table",
- Map.of("tableName", "TB_CUST_BAS", "owner", "DAPADM"));
- }
- if (normalized.contains("템플릿")) {
- return new PlannedTool("cus", "cmm_template_url", Map.of("templateId", "TPL_001"));
- }
- if (normalized.contains("sol 의뢰서 상세") || normalized.contains("sol 상세")) {
- return new PlannedTool("cus", "sol_request_detail", Map.of("srId", "SR-001"));
- }
- if (normalized.contains("sol 의뢰서") || normalized.contains("sol 목록")) {
- return new PlannedTool("cus", "sol_request_list",
- Map.of("status", "진행중", "period", "1개월", "target", "나의 업무"));
- }
- if (normalized.contains("보험금 청구")) {
- return new PlannedTool("cus", "ins_insurance_processor", Map.of(
- "claimNumber", "CLM20230001", "claimAmount", 1500000, "claimDate", "2026-08-12"));
- }
- if (normalized.contains("가입설계 한도") || normalized.contains("onnba3011")) {
- return new PlannedTool("cus", "oth_onnba3011_call", Map.of(
- "dalScCd", "1", "cstSucoRltyCd", "01", "csNo", "000000000001"));
- }
- if (normalized.contains("cus") && (normalized.contains("환율") || normalized.contains("exchange"))) {
- return new PlannedTool("cus", "smp_exchange_inquiry", Map.of("currencyCode", "USD"));
- }
- if (normalized.contains("cus") && (normalized.contains("날씨") || normalized.contains("weather"))) {
- return new PlannedTool("cus", "smp_weather_inquiry", Map.of("city", "서울"));
- }
- if (normalized.contains("오늘의 명언") || normalized.contains("명언")) {
- return new PlannedTool("cus", "smp_quote_daily", Map.of("category", "속담"));
- }
- if (normalized.contains("tool 파트") || normalized.contains("툴 파트") || normalized.contains("파트 구성원")) {
- return new PlannedTool("cus", "smp_team_list", Map.of("teamName", "TOOL"));
- }
- if (normalized.contains("공휴일") || normalized.contains("휴일") || normalized.contains("holiday")) {
- return new PlannedTool("external", "external.public_holiday_lookup", Map.of(
- "countryCode", "KR",
- "year", Year.now().getValue()));
- }
- if ((normalized.contains("고객") || normalized.contains("customer"))
- && !normalized.contains("티켓") && !normalized.contains("ticket")) {
- return new PlannedTool("business", "business.customer_search", Map.of("keyword", "C-1001"));
- }
- if (normalized.contains("주문") || normalized.contains("order")) {
- return new PlannedTool("business", "business.order_status", Map.of("orderId", "O-9001"));
- }
- if (normalized.contains("티켓") || normalized.contains("ticket")) {
- return new PlannedTool("business", "business.ticket_create", Map.of(
- "title", "Portal test ticket",
- "priority", "normal",
- "description", "Created from the Portal Agent test screen"));
- }
- if (normalized.contains("좌표") || normalized.contains("지오코딩") || normalized.contains("geocoding")) {
- return new PlannedTool("external", "external.geocoding_lookup", Map.of("city", "Seoul", "language", "ko"));
- }
- if (normalized.contains("국가") || normalized.contains("나라") || normalized.contains("country")) {
- return new PlannedTool("external", "external.country_info_lookup", Map.of("countryCode", "KR"));
- }
- if (normalized.contains("환율") || normalized.contains("달러") || normalized.contains("usd")
- || normalized.contains("exchange")) {
- return new PlannedTool("external", "external.exchange_rate", Map.of("from", "USD", "to", "KRW"));
- }
- return new PlannedTool("external", "external.weather_lookup", Map.of(
- "city", city(normalized),
- "timezone", "Asia/Seoul"));
- }
-
- private static String toolPrefix(String toolName) {
- int dot = toolName.indexOf('.');
- if (dot > 0) {
- return toolName.substring(0, dot);
- }
- int underscore = toolName.indexOf('_');
- return underscore > 0 ? toolName.substring(0, underscore) : toolName;
- }
-
- private static String city(String message) {
- if (message.contains("부산") || message.contains("busan")) {
- return "Busan";
- }
- if (message.contains("대구") || message.contains("daegu")) {
- return "Daegu";
- }
- if (message.contains("인천") || message.contains("incheon")) {
- return "Incheon";
- }
- return "Seoul";
- }
-
- private JsonNode firstToolText(JsonNode mcpBody) {
- JsonNode content = mcpBody.path("result").path("content");
- if (!content.isArray() || content.isEmpty()) {
- return mcpBody;
- }
- String text = content.get(0).path("text").asText("");
- if (text.isBlank()) {
- return content.get(0);
- }
- try {
- return objectMapper.readTree(text);
- } catch (Exception ignored) {
- return objectMapper.createObjectNode().put("text", text);
- }
- }
-
- static String answer(String toolName, JsonNode payload) {
- boolean directToolResult = !payload.has("success") && !payload.has("error");
- if (!directToolResult && !payload.path("success").asBoolean(false)) {
- String error = payload.path("error").asText();
- if (error.isBlank()) {
- error = payload.path("text").asText("도구 호출에 실패했습니다.");
- }
- return "도구 실행이 실패했습니다: " + error;
- }
- JsonNode data = directToolResult ? payload : payload.path("data");
- if ("external.public_holiday_lookup".equals(toolName)) {
- JsonNode holidays = data.path("holidays");
- List preview = new java.util.ArrayList<>();
- if (holidays.isArray()) {
- for (int index = 0; index < Math.min(holidays.size(), 5); index++) {
- JsonNode holiday = holidays.get(index);
- preview.add("%s %s".formatted(
- holiday.path("date").asText(),
- holiday.path("localName").asText(holiday.path("name").asText())));
- }
- }
- return "%s년 대한민국 공휴일은 총 %s일입니다.%s".formatted(
- data.path("year").asText(String.valueOf(Year.now().getValue())),
- holidays.isArray() ? holidays.size() : 0,
- preview.isEmpty() ? "" : " 주요 공휴일: " + String.join(", ", preview));
- }
- if (toolName.startsWith("business.")) {
- return "%s 실행 결과: %s".formatted(toolName, data.toString());
- }
- if (isCusTool(toolName)) {
- return "%s 실행 결과: %s".formatted(toolName, data.toString());
- }
- if ("external.geocoding_lookup".equals(toolName)) {
- return "도시 좌표 조회 결과: " + data.path("results").toString();
- }
- if ("external.country_info_lookup".equals(toolName)) {
- return "국가 정보 조회 결과: " + data.path("countries").toString();
- }
- if ("external.exchange_rate".equals(toolName)) {
- return "%s 기준 %s/%s 환율은 %s입니다.".formatted(
- data.path("date").asText("현재"),
- data.path("base").asText("USD"),
- data.path("target").asText("KRW"),
- data.path("rate").asText());
- }
- return "%s 현재 기온은 %s도이고 풍속은 %s입니다.".formatted(
- data.path("city").asText("해당 지역"),
- data.path("temperature").asText(),
- data.path("windSpeed").asText());
- }
-
- private static boolean isCusTool(String toolName) {
- return toolName.startsWith("cmm_") || toolName.startsWith("ins_")
- || toolName.startsWith("oth_") || toolName.startsWith("smp_")
- || toolName.startsWith("sol_");
- }
-
- private ResponseEntity postMcp(String routeKey, Map payload, boolean includeProtocolHeaders) {
- String sessionId = includeProtocolHeaders ? latestSessionId.get() : null;
- String endpointUrl = mcpEndpointUrl(routeKey);
- Map headers = new LinkedHashMap<>();
- headers.put("Content-Type", MediaType.APPLICATION_JSON_VALUE);
- if (includeProtocolHeaders) {
- headers.put(MCP_PROTOCOL_VERSION_HEADER, properties.mcp().protocolVersion());
- if (sessionId != null && !sessionId.isBlank()) {
- headers.put(MCP_SESSION_ID_HEADER, sessionId);
- }
- }
- log.info("Outbound MCP request: method=POST, uri={}, headers={}, body={}",
- endpointUrl, headers, payload);
-
- RestClient.RequestBodySpec spec = restClient.post()
- .uri(endpointUrl)
- .contentType(MediaType.APPLICATION_JSON);
- if (includeProtocolHeaders) {
- spec.header(MCP_PROTOCOL_VERSION_HEADER, properties.mcp().protocolVersion());
- if (sessionId != null && !sessionId.isBlank()) {
- spec.header(MCP_SESSION_ID_HEADER, sessionId);
- }
- }
- ResponseEntity response = spec.body(payload).retrieve().toEntity(JsonNode.class);
- log.info("Inbound MCP response: status={}, headers={{{}={}}}, body={}",
- response.getStatusCode().value(), MCP_SESSION_ID_HEADER,
- response.getHeaders().getFirst(MCP_SESSION_ID_HEADER), response.getBody());
- return response;
- }
-
- private String mcpEndpointUrl(String routeKey) {
- String baseUrl = properties.mcp().endpointUrl().replaceAll("/+$", "");
- String normalizedRouteKey = normalizeRouteKey(routeKey);
- return normalizedRouteKey.isBlank() ? baseUrl : baseUrl + "/" + normalizedRouteKey;
- }
-
- private String normalizeRouteKey(String routeKey) {
- if (routeKey == null || routeKey.isBlank()) {
- return defaultRouteKey();
- }
- return routeKey.trim();
- }
-
- private String defaultRouteKey() {
- String configured = properties.portal().routeKey();
- return configured == null || configured.isBlank() ? "cus" : configured.trim();
- }
-
- private String routeKeyForTool(String toolName) {
- if (toolName == null || toolName.isBlank()) {
- return defaultRouteKey();
- }
- if (toolName.startsWith("business.")) {
- return "business";
- }
- if (isCusTool(toolName)) {
- return "cus";
- }
- return "external";
- }
- private String masked() {
- return properties.toolServer().apiKey() == null || properties.toolServer().apiKey().isBlank()
- ? ""
- : "********";
- }
-
- private Map jsonRpc(String method, Map params) {
- return Map.of(
- "jsonrpc", "2.0",
- "id", ids.getAndIncrement(),
- "method", method,
- "params", params);
- }
-
- private Map notification(String method) {
- return Map.of(
- "jsonrpc", "2.0",
- "method", method,
- "params", Map.of());
- }
-
- private Map response(String action, ResponseEntity response) {
- Map result = new LinkedHashMap<>();
- result.put("action", action);
- result.put("httpStatus", response.getStatusCode().value());
- result.put("mcpSessionId", response.getHeaders().getFirst(MCP_SESSION_ID_HEADER));
- result.put("body", response.getBody() == null ? objectMapper.createObjectNode() : response.getBody());
- return result;
- }
-
- public record ToolCallRequest(String name, Map arguments) {
- }
-
- public record ChatRequest(String message) {
- }
-
- record PlannedTool(String routeKey, String name, Map arguments) {
- }
-}
diff --git a/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalApiExceptionHandler.java b/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalApiExceptionHandler.java
deleted file mode 100644
index b2d287d..0000000
--- a/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalApiExceptionHandler.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.example.agenttest;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.RestControllerAdvice;
-import org.springframework.web.server.ResponseStatusException;
-
-@RestControllerAdvice(assignableTypes = PortalBundleController.class)
-public class PortalApiExceptionHandler {
-
- @ExceptionHandler(ResponseStatusException.class)
- public ResponseEntity