Skip to content
Merged

Mcp #31

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions .github/workflows/test-belyportal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Test BELY Portal

on:
pull_request:
paths:
- 'src/java/LogrPortal/src/java/**'
- 'src/java/LogrPortal/test/**'
- '.github/workflows/test-belyportal.yml'
workflow_dispatch:

jobs:
test:
name: Compile and Run BELY Portal Java Tests
runs-on: ubuntu-latest

defaults:
run:
working-directory: src/java/LogrPortal

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 11
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '11'

- name: Fetch JAXB API (removed from the JDK since Java 11)
run: |
set -euo pipefail
# javax.xml.bind was dropped from the JDK in Java 11 (JEP 320). The
# application normally compiles against Payara's bundled JAXB
# implementation via NetBeans' j2ee.platform.classpath, which this
# CI-only javac/java build path does not use. Fetch a standalone
# JAXB API jar (interfaces only, sufficient for compilation) instead.
mkdir -p build/extra-libs
curl -fsSL -o build/extra-libs/jaxb-api-2.3.1.jar \
https://repo1.maven.org/maven2/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar

- name: Generate default PluginRegistrar
run: |
set -euo pipefail
# src/java/.../plugins/support/ is gitignored (see .gitignore) and is
# normally populated by tools/developer_tools/logr_plugins at deploy
# time based on which plugins are installed. CdbPluginManager.java
# imports PluginRegistrar unconditionally, so a fresh checkout can't
# compile without it. Generate the no-plugins-installed version.
REGISTRAR_DIR=src/java/gov/anl/aps/logr/portal/plugins/support
REGISTRAR_FILE="$REGISTRAR_DIR/PluginRegistrar.java"
if [ ! -f "$REGISTRAR_FILE" ]; then
mkdir -p "$REGISTRAR_DIR"
cat > "$REGISTRAR_FILE" <<'EOF'
package gov.anl.aps.logr.portal.plugins.support;

import gov.anl.aps.logr.portal.plugins.CdbPluginManager;

public abstract class PluginRegistrar {

public static void registerPlugins(CdbPluginManager cdbPluginManager) {
// No plugins installed for this CI build.
}
}
EOF
fi

- name: Compile main sources
run: |
set -euo pipefail
mkdir -p build/classes
find src/java -name '*.java' > build/main-sources.txt
javac -proc:none -source 11 -target 11 \
-cp 'lib/*:build/extra-libs/*' \
-d build/classes \
@build/main-sources.txt

- name: Compile test sources
run: |
set -euo pipefail
mkdir -p build/test-classes
find test -name '*.java' > build/test-sources.txt
javac -proc:none -source 11 -target 11 \
-cp 'lib/*:build/extra-libs/*:build/classes' \
-d build/test-classes \
@build/test-sources.txt

- name: Discover and run tests
run: |
set -uo pipefail
CP="lib/*:build/extra-libs/*:build/classes:build/test-classes"

TEST_CLASSES=()
while IFS= read -r class; do
TEST_CLASSES+=("$class")
done < <(
find test -name '*Test.java' \
| sed -e 's#^test/##' -e 's/\.java$//' -e 's#/#.#g' \
| sort
)

if [ "${#TEST_CLASSES[@]}" -eq 0 ]; then
echo "::error::No test classes discovered under test/ (pattern *Test.java)."
exit 1
fi

echo "Discovered ${#TEST_CLASSES[@]} test classes:"
printf ' %s\n' "${TEST_CLASSES[@]}"

FAILED=0
for class in "${TEST_CLASSES[@]}"; do
echo "::group::${class}"
java -cp "$CP" "$class"
rc=$?
if [ "$rc" -eq 0 ]; then
echo "${class}: PASS"
else
echo "${class}: FAIL (exit ${rc})"
FAILED=1
fi
echo "::endgroup::"
done

if [ "$FAILED" -ne 0 ]; then
echo "::error::One or more test classes failed."
exit 1
fi
echo "All test classes passed."
7 changes: 3 additions & 4 deletions src/java/LogrPortal/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ dist.dir=dist
dist.ear.war=${dist.dir}/${war.ear.name}
dist.javadoc.dir=${dist.dir}/javadoc
dist.war=${dist.dir}/${war.name}
endorsed.classpath=\
${libs.javaee-endorsed-api-7.0.classpath}
endorsed.classpath=
excludes=
file.reference.bcmail-jdk14-1.38.jar=lib/bcmail-jdk14-1.38.jar
file.reference.bcprov-jdk14-1.38.jar=lib/bcprov-jdk14-1.38.jar
Expand Down Expand Up @@ -184,8 +183,8 @@ javac.processorpath=\
${javac.classpath}:\
${libs.eclipselink.classpath}:\
${libs.eclipselinkmodelgen.classpath}
javac.source=1.8
javac.target=1.8
javac.source=11
javac.target=11
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
Expand Down
5 changes: 5 additions & 0 deletions src/java/LogrPortal/src/java/cdb.portal.properties.template
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ cdb.portal.handler.http.linkDisplayLength=32
# CDB Web Service
cdb.webService.url=http://localhost:10232/cdb
cdb.permanentContextRoot.url=http://localhost:8080/cdb

# Model Context Protocol (MCP) endpoint at /api/mcp
cdb.portal.mcp.enabled=true
cdb.portal.mcp.requireAuth=false
cdb.portal.mcp.allowedOrigins=
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
public enum CallSource {

API("API"),
Portal("Portal");
Portal("Portal"),
MCP("MCP");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ private Set<Class<?>> getRestResourceClasses() {
resources.add(gov.anl.aps.logr.rest.routes.DomainRoute.class);
resources.add(gov.anl.aps.logr.rest.routes.DownloadRoute.class);
resources.add(gov.anl.aps.logr.rest.routes.LogbookRoute.class);
resources.add(gov.anl.aps.logr.rest.mcp.McpRoute.class);
resources.add(gov.anl.aps.logr.rest.routes.NotificationConfigurationRoute.class);
resources.add(gov.anl.aps.logr.rest.routes.PropertyValueRoute.class);
resources.add(gov.anl.aps.logr.rest.routes.SearchRoute.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) UChicago Argonne, LLC. All rights reserved.
* See LICENSE file.
*/
package gov.anl.aps.logr.rest.mcp;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/** Shared constants for the MCP endpoint: private ObjectMapper, header names, error codes, server identity. */
public final class McpConstants {

private McpConstants() {
}

public static final ObjectMapper MAPPER = new ObjectMapper();

public static final String PROTOCOL_VERSION = "2026-07-28";
public static final List<String> SUPPORTED_PROTOCOL_VERSIONS = Collections.singletonList(PROTOCOL_VERSION);

// Legacy (pre-2026-07-28) revisions served via a sessionless "initialize" handshake; see McpRoute#initializeLegacy.
public static final List<String> LEGACY_PROTOCOL_VERSIONS = Arrays.asList("2025-11-25", "2025-06-18", "2025-03-26");
public static final String LEGACY_PROTOCOL_VERSION_DEFAULT = "2025-11-25";

public static final String HEADER_PROTOCOL_VERSION = "MCP-Protocol-Version";
public static final String HEADER_METHOD = "Mcp-Method";
public static final String HEADER_NAME = "Mcp-Name";
public static final String HEADER_ORIGIN = "Origin";
public static final String HEADER_TOKEN = "token";

public static final String META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
public static final String META_CLIENT_INFO = "io.modelcontextprotocol/clientInfo";
public static final String META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
public static final String META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";

public static final int ERR_PARSE_ERROR = -32700;
public static final int ERR_INVALID_REQUEST = -32600;
public static final int ERR_METHOD_NOT_FOUND = -32601;
public static final int ERR_INVALID_PARAMS = -32602;
public static final int ERR_INTERNAL_ERROR = -32603;
public static final int ERR_UNAUTHORIZED = -32001;
public static final int ERR_HEADER_MISMATCH = -32020;
public static final int ERR_UNSUPPORTED_VERSION = -32022;

public static final String SERVER_NAME = "bely";
public static final String SERVER_TITLE = "BELY Electronic Logbook";
public static final String SERVER_VERSION = "1.0.0";

public static final String INSTRUCTIONS =
"BELY (Best Electronic Logbook Yet) is an electronic logbook. A \"log document\" "
+ "(sometimes just \"document\") is a dated container such as an operations shift log; "
+ "each document belongs to a logbook type (e.g. \"Ops Shift\") and a system (e.g. "
+ "\"Storage Ring\"). A document may have \"sections\" — sub-documents that group their "
+ "own log entries under a heading within the parent document. A \"log entry\" (or "
+ "\"log\") is one timestamped note inside a document or section, and may have replies "
+ "and reactions. Use bely_list_lookups to discover valid logbook type and system ids "
+ "before filtering searches or listings by them. Use bely_search to find documents or "
+ "entries by text; use bely_get_log_document / bely_get_log_entry to read one in full "
+ "once you know its id. All tools are read-only.";

public static final String PROP_ENABLED = "cdb.portal.mcp.enabled";
public static final String PROP_REQUIRE_AUTH = "cdb.portal.mcp.requireAuth";
public static final String PROP_ALLOWED_ORIGINS = "cdb.portal.mcp.allowedOrigins";
}
Loading
Loading