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

-
Hardcoded Registry + Agent Backend + MCP Tool Call
-
-
Loading configuration...
-
- -
-
-
-

Portal Registry

-

필요할 때 MCP가 Registry를 다시 확인하도록 revision을 갱신합니다.

- -
- - -
-
- 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> handle(ResponseStatusException error) { - Map body = new LinkedHashMap<>(); - body.put("status", error.getStatusCode().value()); - body.put("error", error.getStatusCode().toString()); - body.put("message", error.getReason() == null ? "Request failed" : error.getReason()); - return ResponseEntity.status(error.getStatusCode()).body(body); - } -} diff --git a/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalBundleController.java b/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalBundleController.java deleted file mode 100644 index 38ae21c..0000000 --- a/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalBundleController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.example.agenttest; - -import com.example.agenttest.PortalBundleService.BundleRequest; -import com.example.agenttest.PortalBundleService.BundleView; -import java.util.List; -import org.springframework.http.HttpStatus; -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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/portal/bundles") -public class PortalBundleController { - - private final PortalBundleService bundles; - - public PortalBundleController(PortalBundleService bundles) { - this.bundles = bundles; - } - - @GetMapping - public List list() { - return bundles.list(); - } - - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public BundleView create(@RequestBody BundleRequest request) { - return bundles.create(request); - } - - @PutMapping("/{bundleId}") - public BundleView update( - @PathVariable("bundleId") String bundleId, - @RequestBody BundleRequest request) { - return bundles.update(bundleId, request); - } - - -} diff --git a/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalBundleService.java b/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalBundleService.java deleted file mode 100644 index a52aa88..0000000 --- a/potal/agent-test-backend/src/main/java/com/example/agenttest/PortalBundleService.java +++ /dev/null @@ -1,346 +0,0 @@ -package com.example.agenttest; - -import java.net.URI; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; -import java.util.regex.Pattern; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; -import org.springframework.web.server.ResponseStatusException; - -@Service -public class PortalBundleService { - - private static final Pattern BUNDLE_ID = Pattern.compile("[A-Za-z0-9._-]{1,64}"); - private static final Pattern TOOL_NAME = Pattern.compile("[A-Za-z0-9_./-]{1,64}"); - - private final Map bundles = new ConcurrentHashMap<>(); - private final AtomicLong registryRevision; - - public PortalBundleService(AgentTestProperties properties, RestClient.Builder builder) { - this.registryRevision = new AtomicLong(properties.portal().registryRevision()); - String sharedApiKey = properties.toolServer().apiKey(); - putSeed(new BundleDefinition( - "external-tools", "External Tool Server", - properties.toolServer().manifestUrl(), properties.portal().toolServiceDomain(), - "external.", true, 10, sharedApiKey, - Map.of( - "external.weather_lookup", "/mcp/external.weather_lookup", - "external.exchange_rate", "/mcp/external.exchange_rate", - "external.public_holiday_lookup", "/mcp/external.public_holiday_lookup", - "external.geocoding_lookup", "/mcp/external.geocoding_lookup", - "external.country_info_lookup", "/mcp/external.country_info_lookup"))); - putSeed(new BundleDefinition( - "business-tools", "Business Tool Server", - "http://localhost:9090/tool-manifest", "http://localhost:9090", - "business.", true, 10, sharedApiKey, - Map.of( - "business.customer_search", "/mcp/business.customer_search", - "business.order_status", "/mcp/business.order_status", - "business.ticket_create", "/mcp/business.ticket_create"))); - putSeed(new BundleDefinition( - "was-cus", "DAP WAS CUS Tool Server", - "http://localhost:8084/tool-manifest", "http://localhost:8084", - "", true, 10, null, - cusToolEndpoints())); - } - - public List list() { - return bundles.values().stream() - .map(BundleState::view) - .sorted(Comparator.comparing(view -> view.definition().bundleId())) - .toList(); - } - - public BundleView create(BundleRequest request) { - BundleDefinition definition = validated(request, null); - if (bundles.containsKey(definition.bundleId())) { - throw conflict("Bundle ID already exists: " + definition.bundleId()); - } - ensureManifestUrlUnique(definition.manifestUrl(), null); - BundleState state = new BundleState(definition); - bundles.put(definition.bundleId(), state); - registryRevision.incrementAndGet(); - return state.view(); - } - - public BundleView update(String bundleId, BundleRequest request) { - BundleState current = required(bundleId); - BundleDefinition definition = validated(request, current.definition().apiKey()); - if (!bundleId.equals(definition.bundleId())) { - throw badRequest("Bundle ID cannot be changed"); - } - ensureManifestUrlUnique(definition.manifestUrl(), bundleId); - current.update(definition); - registryRevision.incrementAndGet(); - return current.view(); - } - - public Map portalRegistry(String routeKey) { - List> services = bundles.values().stream() - .filter(state -> state.definition().enabled()) - .filter(state -> belongsToRoute(state.definition(), routeKey)) - .map(state -> service(state.definition())) - .sorted(Comparator.comparing(item -> String.valueOf(item.get("serviceKey")))) - .toList(); - return Map.of( - "routeKey", routeKey, - "registryRevision", registryRevision.get(), - "toolServices", services); - } - - public Map portalRegistry() { - List> routes = bundles.values().stream() - .filter(state -> state.definition().enabled()) - .map(state -> routeKey(state.definition())) - .distinct() - .sorted() - .map(routeKey -> Map.of( - "routeKey", routeKey, - "toolServices", bundles.values().stream() - .filter(state -> state.definition().enabled()) - .filter(state -> belongsToRoute(state.definition(), routeKey)) - .map(state -> service(state.definition())) - .sorted(Comparator.comparing(item -> String.valueOf(item.get("serviceKey")))) - .toList())) - .toList(); - return Map.of( - "registryRevision", registryRevision.get(), - "routes", routes); - } - - private boolean belongsToRoute(BundleDefinition definition, String routeKey) { - String normalizedRoute = routeKey == null ? "" : routeKey.trim().toLowerCase(java.util.Locale.ROOT); - return routeKey(definition).equalsIgnoreCase(normalizedRoute); - } - - private String routeKey(BundleDefinition definition) { - String prefix = definition.namePrefix(); - if (prefix == null || prefix.isBlank()) { - return definition.bundleId().startsWith("was-") - ? definition.bundleId().substring("was-".length()) - : definition.bundleId(); - } - int dot = prefix.indexOf('.'); - int underscore = prefix.indexOf('_'); - int end = dot >= 0 && underscore >= 0 ? Math.min(dot, underscore) : Math.max(dot, underscore); - return end > 0 ? prefix.substring(0, end) : prefix.replaceAll("[._]+$", ""); - } - - public Map screenRegistry(String routeKey, String mcpEndpointUrl) { - Map route = new LinkedHashMap<>(); - route.put("routeKey", routeKey); - route.put("displayName", "Portal MCP Route"); - route.put("routePath", "/mcp/" + routeKey); - route.put("mcpEndpointUrl", mcpEndpointUrl); - route.put("status", "ACTIVE"); - - List> services = list().stream().map(view -> { - BundlePublicDefinition item = view.definition(); - Map service = new LinkedHashMap<>(); - service.put("serviceKey", item.bundleId()); - service.put("displayName", item.toolServerName()); - service.put("manifestUrl", item.manifestUrl()); - service.put("baseEndpoint", item.baseUrl()); - service.put("namePrefix", item.namePrefix()); - service.put("enabled", item.enabled()); - service.put("manifestPollIntervalSeconds", item.manifestPollIntervalSeconds()); - service.put("status", item.enabled() ? "ACTIVE" : "INACTIVE"); - return service; - }).toList(); - return Map.of("mcpRoutes", List.of(route), "toolServices", services, - "mappings", services.stream().map(service -> Map.of( - "routeKey", routeKey, - "serviceKey", service.get("serviceKey"), - "status", service.get("status"), - "source", "portal-bundle-registry", - "registryRevision", registryRevision.get())).toList()); - } - - public long bumpRevision() { - return registryRevision.incrementAndGet(); - } - - private Map service(BundleDefinition definition) { - URI manifest = URI.create(definition.manifestUrl()); - String manifestPath = manifest.getRawPath(); - Map service = new LinkedHashMap<>(); - service.put("serviceKey", definition.bundleId()); - service.put("displayName", definition.toolServerName()); - service.put("serviceDomain", definition.baseUrl()); - service.put("manifestPath", manifestPath == null || manifestPath.isBlank() ? "/tool-manifest" : manifestPath); - service.put("executeBasePath", ""); - service.put("namePrefix", definition.namePrefix()); - service.put("toolEndpoints", definition.toolEndpoints()); - service.put("status", definition.enabled() ? "ACTIVE" : "INACTIVE"); - return service; - } - - private BundleDefinition validated(BundleRequest request, String existingApiKey) { - if (request == null) { - throw badRequest("Request body is required"); - } - String bundleId = requiredText(request.bundleId(), "bundleId"); - if (!BUNDLE_ID.matcher(bundleId).matches()) { - throw badRequest("Bundle ID is invalid"); - } - String name = requiredText(request.toolServerName(), "toolServerName"); - String manifestUrl = validUrl(request.manifestUrl(), "manifestUrl"); - String baseUrl = validUrl(request.baseUrl(), "baseUrl").replaceAll("/+$", ""); - String prefix = request.namePrefix() == null ? "" : request.namePrefix().trim(); - if (!prefix.isBlank() && !prefix.endsWith(".") && !prefix.endsWith("_")) { - throw badRequest("Tool name prefix must end with '.' or '_'"); - } - long interval = request.manifestPollIntervalSeconds(); - if (interval < 5 || interval > 86_400) { - throw badRequest("Manifest poll interval must be between 5 and 86400 seconds"); - } - Map endpoints = request.toolEndpoints() == null - ? Map.of() : Map.copyOf(request.toolEndpoints()); - endpoints.forEach((toolName, path) -> { - if (!TOOL_NAME.matcher(toolName).matches() - || (!prefix.isBlank() && !toolName.startsWith(prefix))) { - throw badRequest("Tool endpoint name must start with " + prefix + ": " + toolName); - } - if (path == null || !path.startsWith("/") || path.startsWith("//")) { - throw badRequest("Tool endpoint path must start with a single '/': " + toolName); - } - }); - String apiKey = request.apiKey() == null || request.apiKey().isBlank() ? existingApiKey : request.apiKey(); - return new BundleDefinition(bundleId, name, manifestUrl, baseUrl, prefix, - request.enabled(), interval, apiKey, endpoints); - } - - private Map cusToolEndpoints() { - List names = List.of( - "cmm_comcode_lookup", "cmm_customer_tool", "cmm_meta_table", "cmm_template_url", - "ins_insurance_processor", "oth_onnba3011_call", - "smp_exchange_inquiry", "smp_quote_daily", "smp_team_list", - "smp_weather_inquiry", "sol_request_detail", "sol_request_list"); - Map endpoints = new LinkedHashMap<>(); - names.forEach(name -> endpoints.put(name, "/mcp/" + name)); - return Map.copyOf(endpoints); - } - - private void ensureManifestUrlUnique(String manifestUrl, String excludedBundleId) { - boolean duplicate = bundles.values().stream().anyMatch(state -> - !state.definition().bundleId().equals(excludedBundleId) - && state.definition().manifestUrl().equalsIgnoreCase(manifestUrl)); - if (duplicate) { - throw conflict("Manifest URL already exists: " + manifestUrl); - } - } - - private BundleState required(String bundleId) { - BundleState state = bundles.get(bundleId); - if (state == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Bundle not found: " + bundleId); - } - return state; - } - - private void putSeed(BundleDefinition definition) { - bundles.put(definition.bundleId(), new BundleState(definition)); - } - - private String requiredText(String value, String field) { - if (value == null || value.isBlank()) { - throw badRequest(field + " is required"); - } - return value.trim(); - } - - private String validUrl(String value, String field) { - String text = requiredText(value, field); - try { - URI uri = URI.create(text); - if (!uri.isAbsolute() || !("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme())) - || uri.getHost() == null) { - throw new IllegalArgumentException(); - } - return uri.toString(); - } catch (RuntimeException error) { - throw badRequest(field + " must be an absolute HTTP(S) URL"); - } - } - - private ResponseStatusException badRequest(String message) { - return new ResponseStatusException(HttpStatus.BAD_REQUEST, message); - } - - private ResponseStatusException conflict(String message) { - return new ResponseStatusException(HttpStatus.CONFLICT, message); - } - - public record BundleRequest( - String bundleId, - String toolServerName, - String manifestUrl, - String baseUrl, - String namePrefix, - boolean enabled, - long manifestPollIntervalSeconds, - String apiKey, - Map toolEndpoints) { - } - - public record BundleDefinition( - String bundleId, - String toolServerName, - String manifestUrl, - String baseUrl, - String namePrefix, - boolean enabled, - long manifestPollIntervalSeconds, - String apiKey, - Map toolEndpoints) { - } - - public record BundlePublicDefinition( - String bundleId, - String toolServerName, - String manifestUrl, - String baseUrl, - String namePrefix, - boolean enabled, - long manifestPollIntervalSeconds, - boolean apiKeyConfigured, - Map toolEndpoints) { - } - - public record BundleView( - BundlePublicDefinition definition, - String manifestRevision, - String lastSynchronizedAt, - String lastError, - List> tools) { - } - - private static final class BundleState { - private volatile BundleDefinition definition; - - private BundleState(BundleDefinition definition) { - this.definition = definition; - } - - private synchronized void update(BundleDefinition definition) { - this.definition = definition; - } - - private BundleDefinition definition() { return definition; } - - private BundleView view() { - BundleDefinition item = definition; - BundlePublicDefinition publicDefinition = new BundlePublicDefinition( - item.bundleId(), item.toolServerName(), item.manifestUrl(), item.baseUrl(), - item.namePrefix(), item.enabled(), item.manifestPollIntervalSeconds(), - item.apiKey() != null && !item.apiKey().isBlank(), item.toolEndpoints()); - return new BundleView(publicDefinition, null, null, null, List.of()); - } - } -} diff --git a/potal/agent-test-backend/src/main/resources/application.yml b/potal/agent-test-backend/src/main/resources/application.yml deleted file mode 100644 index 7c5fa8f..0000000 --- a/potal/agent-test-backend/src/main/resources/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/src/main/resources/static/index.html b/potal/agent-test-backend/src/main/resources/static/index.html deleted file mode 100644 index f47ca89..0000000 --- a/potal/agent-test-backend/src/main/resources/static/index.html +++ /dev/null @@ -1,625 +0,0 @@ - - - - - - AX HUB Portal PoC - - - -
-
-

AX HUB Portal PoC

-
Hardcoded Registry + Agent Backend + MCP Tool Call
-
-
Loading configuration...
-
- -
-
-
-

Portal Registry

-

필요할 때 MCP가 Registry를 다시 확인하도록 revision을 갱신합니다.

- -
- - -
-
- Tool Server 설정 -
- - - - - - - - - - - -
- 고급 설정 - - - - - - -
-
- - -
-
-
-
- -
-
- MCP 연결 테스트 - - -
- - - -
-
-
-
-
- -
-
-
- Agent 테스트 - - -
- - - - - - - - - - - - - - - - - - - - - -
-
Agent 응답 대기 중
-
-
- -
-
- Tool 직접 호출 - - -
-
- - -
-
- - -
-
-
- - - -
-
-
- -
-
- 상세 응답 보기 -
Waiting...
-
-
-
-
- - - - diff --git a/potal/agent-test-backend/src/test/java/com/example/agenttest/McpProxyControllerTest.java b/potal/agent-test-backend/src/test/java/com/example/agenttest/McpProxyControllerTest.java deleted file mode 100644 index 89a0942..0000000 --- a/potal/agent-test-backend/src/test/java/com/example/agenttest/McpProxyControllerTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.example.agenttest; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.servlet.MockMvc; - -@SpringBootTest -@AutoConfigureMockMvc -class McpProxyControllerTest { - - @Test - void treatsDirectExternalToolPayloadAsSuccessfulResult() throws Exception { - var payload = new com.fasterxml.jackson.databind.ObjectMapper().readTree( - "{\"year\":2026,\"holidays\":[{\"date\":\"2026-01-01\",\"localName\":\"New Year\"}]}" ); - - assertThat(McpProxyController.answer("external.public_holiday_lookup", payload)) - .contains("2026", "1", "2026-01-01", "New Year") - .doesNotContain("실패"); - } - - @Test - void treatsDirectOthToolPayloadAsSuccessfulResult() throws Exception { - var payload = new com.fasterxml.jackson.databind.ObjectMapper().readTree( - "{\"codeList\":[{\"code\":\"CD001\",\"codeName\":\"진행중\"}]}"); - - assertThat(McpProxyController.answer("cmm_comcode_lookup", payload)) - .contains("cmm_comcode_lookup 실행 결과") - .contains("CD001") - .doesNotContain("실패"); - } - - @Autowired - private MockMvc mockMvc; - - @Test - void portalRegistryReturnsOkForExternalRoute() throws Exception { - mockMvc.perform(get("/api/portal/registry/external")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.routeKey").value("external")) - .andExpect(jsonPath("$.toolServices.length()").value(1)) - .andExpect(jsonPath("$.toolServices[0].serviceKey").value("external-tools")); - } - - @Test - void bumpsPortalRegistryRevision() throws Exception { - mockMvc.perform(post("/api/portal/registry/external/revision")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.routeKey").value("external")) - .andExpect(jsonPath("$.registryRevision").isNumber()); - } - - @Test - void aggregatePortalRegistryReturnsEveryRouteAndToolService() throws Exception { - mockMvc.perform(get("/api/portal/registry")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.registryRevision").isNumber()) - .andExpect(jsonPath("$.routes[?(@.routeKey=='external')].toolServices[0].serviceKey") - .value("external-tools")) - .andExpect(jsonPath("$.routes[?(@.routeKey=='business')].toolServices[0].serviceKey") - .value("business-tools")) - .andExpect(jsonPath("$.routes[?(@.routeKey=='cus')].toolServices[0].serviceKey") - .value("was-cus")); - } - - @Test - void exposesBusinessBundleAndEndpointMappingsWithoutApiKey() throws Exception { - mockMvc.perform(get("/api/portal/bundles")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].definition.apiKey").doesNotExist()) - .andExpect(jsonPath("$[0].definition.apiKeyConfigured").isBoolean()) - .andExpect(jsonPath("$[0].definition.bundleId").value("business-tools")) - .andExpect(jsonPath("$[0].definition.toolEndpoints['business.customer_search']") - .value("/mcp/business.customer_search")); - - mockMvc.perform(get("/api/portal/registry/business")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.toolServices[0].serviceKey").value("business-tools")) - .andExpect(jsonPath("$.toolServices[0].toolEndpoints['business.ticket_create']") - .value("/mcp/business.ticket_create")); - } - - @Test - void selectsPublicHolidayToolForKoreanHolidayRequest() { - McpProxyController.PlannedTool plan = McpProxyController.planRequest("지금 현재 공휴일 조회해줘"); - - assertThat(plan.name()).isEqualTo("external.public_holiday_lookup"); - assertThat(plan.routeKey()).isEqualTo("external"); - assertThat(plan.arguments()).containsEntry("countryCode", "KR").containsKey("year"); - } - - @Test - void selectsEveryManifestToolFromNaturalLanguageExamples() { - assertThat(McpProxyController.planRequest("서울 날씨 조회").name()).isEqualTo("external.weather_lookup"); - assertThat(McpProxyController.planRequest("달러 환율 조회").name()).isEqualTo("external.exchange_rate"); - assertThat(McpProxyController.planRequest("서울 좌표 조회").name()).isEqualTo("external.geocoding_lookup"); - assertThat(McpProxyController.planRequest("한국 국가 정보 조회").name()).isEqualTo("external.country_info_lookup"); - assertThat(McpProxyController.planRequest("고객 검색").name()).isEqualTo("business.customer_search"); - assertThat(McpProxyController.planRequest("주문 상태 조회").name()).isEqualTo("business.order_status"); - assertThat(McpProxyController.planRequest("고객 지원 티켓 생성").name()).isEqualTo("business.ticket_create"); - assertThat(McpProxyController.planRequest("메타 공통코드 조회").name()).isEqualTo("cmm_comcode_lookup"); - assertThat(McpProxyController.planRequest("메타 테이블 조회").name()).isEqualTo("cmm_meta_table"); - assertThat(McpProxyController.planRequest("템플릿 다운로드 URL 알려줘").name()).isEqualTo("cmm_template_url"); - assertThat(McpProxyController.planRequest("SOL 의뢰서 목록 조회").name()).isEqualTo("sol_request_list"); - assertThat(McpProxyController.planRequest("SOL 의뢰서 상세 조회").name()).isEqualTo("sol_request_detail"); - assertThat(McpProxyController.planRequest("보험금 청구 처리").name()).isEqualTo("ins_insurance_processor"); - assertThat(McpProxyController.planRequest("가입설계 한도 조회").name()).isEqualTo("oth_onnba3011_call"); - assertThat(McpProxyController.planRequest("CUS 달러 환율 조회").name()).isEqualTo("smp_exchange_inquiry"); - assertThat(McpProxyController.planRequest("CUS 서울 날씨 조회").name()).isEqualTo("smp_weather_inquiry"); - assertThat(McpProxyController.planRequest("오늘의 명언 알려줘").name()).isEqualTo("smp_quote_daily"); - assertThat(McpProxyController.planRequest("TOOL 파트 구성원 조회").name()).isEqualTo("smp_team_list"); - assertThat(McpProxyController.planRequest("고객 검색").routeKey()).isEqualTo("business"); - assertThat(McpProxyController.planRequest("서울 날씨 조회").routeKey()).isEqualTo("external"); - assertThat(McpProxyController.planRequest("메타 테이블 조회").routeKey()).isEqualTo("cus"); - } -} diff --git a/potal/agent-test-backend/src/test/java/com/example/agenttest/PortalApiExceptionHandlerTest.java b/potal/agent-test-backend/src/test/java/com/example/agenttest/PortalApiExceptionHandlerTest.java deleted file mode 100644 index eecc88c..0000000 --- a/potal/agent-test-backend/src/test/java/com/example/agenttest/PortalApiExceptionHandlerTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.agenttest; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Map; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; - -class PortalApiExceptionHandlerTest { - - @Test - void exposesSafeValidationReasonToPortalUi() { - var response = new PortalApiExceptionHandler().handle( - new ResponseStatusException(HttpStatus.BAD_REQUEST, - "Manifest Bundle ID does not match configuration")); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(response.getBody()).containsAllEntriesOf(Map.of( - "status", 400, - "error", "400 BAD_REQUEST", - "message", "Manifest Bundle ID does not match configuration")); - } -} diff --git a/potal/agent-test-backend/src/test/java/com/example/agenttest/PortalBundleServiceTest.java b/potal/agent-test-backend/src/test/java/com/example/agenttest/PortalBundleServiceTest.java deleted file mode 100644 index 6b7517b..0000000 --- a/potal/agent-test-backend/src/test/java/com/example/agenttest/PortalBundleServiceTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.example.agenttest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.example.agenttest.PortalBundleService.BundleRequest; -import java.util.Map; -import org.junit.jupiter.api.Test; -import org.springframework.web.client.RestClient; -import org.springframework.web.server.ResponseStatusException; - -class PortalBundleServiceTest { - - @Test - void exposesSeededWasCusBundleOnCusRoute() { - PortalBundleService service = new PortalBundleService(properties(), RestClient.builder()); - - var bundle = service.list().stream() - .filter(item -> item.definition().bundleId().equals("was-cus")) - .findFirst().orElseThrow(); - - assertThat(bundle.definition().manifestUrl()).isEqualTo("http://localhost:8084/tool-manifest"); - assertThat(bundle.definition().namePrefix()).isEmpty(); - assertThat(bundle.definition().manifestPollIntervalSeconds()).isEqualTo(10); - assertThat(bundle.definition().toolEndpoints()) - .containsEntry("smp_weather_inquiry", "/mcp/smp_weather_inquiry") - .containsEntry("ins_insurance_processor", "/mcp/ins_insurance_processor") - .containsEntry("cmm_customer_tool", "/mcp/cmm_customer_tool") - .hasSize(12); - assertThat(service.portalRegistry("cus").get("toolServices").toString()).contains("was-cus"); - assertThat(service.portalRegistry("external").get("toolServices").toString()).doesNotContain("was-cus"); - } - - @Test - void exposesOnlyEndpointRegistryWithoutManifestSnapshot() { - PortalBundleService service = new PortalBundleService(properties(), RestClient.builder()); - - var business = service.list().stream() - .filter(item -> item.definition().bundleId().equals("business-tools")) - .findFirst().orElseThrow(); - Map registry = service.portalRegistry("business"); - - assertThat(business.manifestRevision()).isNull(); - assertThat(business.lastSynchronizedAt()).isNull(); - assertThat(business.lastError()).isNull(); - assertThat(business.tools()).isEmpty(); - assertThat(registry.get("toolServices").toString()) - .contains("serviceDomain=http://localhost:9090") - .contains("manifestPath=/tool-manifest") - .contains("business.customer_search=/mcp/business.customer_search"); - } - - @Test - void rejectsDuplicateBundleIdManifestUrlAndInvalidUrl() { - PortalBundleService service = new PortalBundleService(properties(), RestClient.builder()); - BundleRequest duplicateId = request("business-tools", "http://localhost:9191/tool-manifest"); - BundleRequest duplicateUrl = request("another-tools", "http://localhost:9090/tool-manifest"); - BundleRequest invalidUrl = request("invalid-tools", "not-a-url"); - - assertThatThrownBy(() -> service.create(duplicateId)) - .isInstanceOf(ResponseStatusException.class) - .hasMessageContaining("409 CONFLICT"); - assertThatThrownBy(() -> service.create(duplicateUrl)) - .isInstanceOf(ResponseStatusException.class) - .hasMessageContaining("409 CONFLICT"); - assertThatThrownBy(() -> service.create(invalidUrl)) - .isInstanceOf(ResponseStatusException.class) - .hasMessageContaining("400 BAD_REQUEST"); - } - - private BundleRequest request(String bundleId, String manifestUrl) { - return new BundleRequest(bundleId, "Test", manifestUrl, "http://localhost:9191", - "test.", true, 60, "", Map.of()); - } - - private AgentTestProperties properties() { - return new AgentTestProperties( - new AgentTestProperties.Mcp("http://localhost:8080/mcp", "2025-11-25"), - new AgentTestProperties.ToolServer("http://localhost:9092/tool-manifest", "secret-key"), - new AgentTestProperties.Portal(1, "external", "http://localhost:9092")); - } - -}