-
Notifications
You must be signed in to change notification settings - Fork 7
feat: nemo check internal plugin #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
8e17ec9
nemocheck internal plugin
julianstephen 7538019
renamed package name not to have dashes
julianstephen 7562856
updated plugin config to read checkserver url
julianstephen 68482b9
fix errors on plugin config init
julianstephen 2dda22e
fix errors on plugin config init
julianstephen 43d2ad4
fix errors on plugin config init
julianstephen 1ff5fc4
pulling in commits from main
julianstephen d330823
formatting fixes
julianstephen 36f6876
logger cleanups
julianstephen 0d6b0d3
updated how to use instructions
julianstephen 56bc3ee
nemo check readme updates
julianstephen af32ea6
better error message propogation
julianstephen 5e50b07
Minor readme fixes
julianstephen 7ece9e9
linting fixes
julianstephen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # Internal NemoCheck Plugin | ||
|
|
||
| ## Prerequisites: Nemo-check server | ||
| * Refer to [orignal repo](https://github.com/m-misiura/demos/tree/main/nemo_openshift/guardrail-checks/deployment) for full instructions | ||
| * Instructions adpated for mcpgateway kind cluster to work with an llm proxy routing to some open ai compatable backend below | ||
|
|
||
| ```bash | ||
| docker pull quay.io/rh-ee-mmisiura/nemo-guardrails:guardrails_checks_with_tools_o1_v1 | ||
| kind load docker-image quay.io/rh-ee-mmisiura/nemo-guardrails:guardrails_checks_with_tools_o1_v1 --name mcp-gateway | ||
| cd plugins-adapter/plugins/examples/nemocheck/k8deploy | ||
| kubectl apply -f config-tools.yaml | ||
| kubectl apply -f server.yaml | ||
|
|
||
| ``` | ||
| ## Installation | ||
|
|
||
| 1. Find url of nemo-check-server service. E.g., from svc in `server.yaml` | ||
| 1. Update `${project_root}/resources/config/config.yaml`. Add the blob below, merge if other `plugin`s or `plugin_dir`s already exists. Sample file [here](/resources/config/nemocheck-internal-config.yaml) | ||
|
|
||
| ```yaml | ||
| # plugins/config.yaml - Main plugin configuration file | ||
| plugins: | ||
| - name: "NemoCheckv2" | ||
| kind: "plugins.examples.nemocheckinternal.plugin.NemoCheckv2" | ||
| description: "Adapter for nemo check server" | ||
| version: "0.1.0" | ||
| hooks: ["tool_pre_invoke", "tool_post_invoke"] | ||
| mode: "enforce" # enforce | permissive | disabled | ||
| config: | ||
| checkserver_url: "http://nemo-guardrails-service:8000/v1/guardrail/checks" | ||
| # Plugin directories to scan | ||
| plugin_dirs: | ||
| - "plugins/examples/nemocheckinternal" # Nemo Check Server plugins | ||
| ``` | ||
|
|
||
| 1. In `config.yaml` ensure key `plugins.config.checkserver_url` points to the correct service | ||
| 1. Start plugin adapter | ||
|
|
||
| # Test | ||
|
|
||
| 1. Open mcp-inspector to the mcp-gateway | ||
| 1. Try running a tool configured/not configured in nemo check config allow list in configmap [E.g.](/plugins/examples/nemocheck/k8deploy/config-tools.yaml) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| """MCP Gateway NemoCheckv2 Plugin - Nemo Check Adapter. | ||
|
|
||
| Copyright 2025 | ||
| SPDX-License-Identifier: Apache-2.0 | ||
| Authors: julianstephen | ||
|
|
||
| """ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| plugins: | ||
| - name: "NemoCheckv2" | ||
| kind: "plugins.examples.nemocheckinternal.plugin.NemoCheckv2" | ||
| description: "Nemo Check Adapter" | ||
| version: "0.1.0" | ||
| author: "julianstephen" | ||
| hooks: ["prompt_pre_fetch", "prompt_post_fetch", "tool_pre_invoke", "tool_post_invoke"] | ||
| tags: ["plugin"] | ||
| mode: "enforce" # enforce | permissive | disabled | ||
| priority: 150 | ||
| conditions: | ||
| # Apply to specific tools/servers | ||
| - server_ids: [] # Apply to all servers | ||
| tenant_ids: [] # Apply to all tenants | ||
| config: | ||
| # Plugin config dict passed to the plugin constructor | ||
|
|
||
| # Plugin directories to scan | ||
| plugin_dirs: | ||
| - "nemocheckv2" | ||
|
|
||
| # Global plugin settings | ||
| plugin_settings: | ||
| parallel_execution_within_band: true | ||
| plugin_timeout: 30 | ||
| fail_on_plugin_error: false | ||
| enable_plugin_api: true | ||
| plugin_health_check_interval: 60 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| description: "Nemo Check Adapter" | ||
| name: NemoCheckv2 | ||
| author: "julianstephen" | ||
| version: "0.1.0" | ||
| available_hooks: | ||
| - "prompt_pre_hook" | ||
| - "prompt_post_hook" | ||
| - "tool_pre_hook" | ||
| - "tool_post_hook" | ||
| default_configs: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| """Nemo Check Adapter. | ||
|
|
||
| Copyright 2025 | ||
| SPDX-License-Identifier: Apache-2.0 | ||
| Authors: julianstephen | ||
|
|
||
| This module loads configurations for plugins. | ||
| """ | ||
|
|
||
| # First-Party | ||
| from mcpgateway.plugins.framework import ( | ||
| Plugin, | ||
| PluginConfig, | ||
| PluginContext, | ||
| PromptPosthookPayload, | ||
| PromptPosthookResult, | ||
| PromptPrehookPayload, | ||
| PromptPrehookResult, | ||
| ToolPostInvokePayload, | ||
| ToolPostInvokeResult, | ||
| ToolPreInvokePayload, | ||
| ToolPreInvokeResult, | ||
| PluginViolation, | ||
| ) | ||
|
|
||
| import logging | ||
| import os | ||
| import requests | ||
| import json | ||
|
|
||
| # Initialize logging service first | ||
| logger = logging.getLogger(__name__) | ||
| log_level = os.getenv("LOGLEVEL", "INFO").upper() | ||
| logger.setLevel(log_level) | ||
|
|
||
| MODEL_NAME = os.getenv( | ||
| "NEMO_MODEL", "meta-llama/llama-3-3-70b-instruct" | ||
| ) # Currently only for logging. | ||
| CHECK_ENDPOINT = os.getenv("CHECK_ENDPOINT", "http://nemo-guardrails-service:8000") | ||
|
|
||
|
|
||
| headers = { | ||
| "Content-Type": "application/json", | ||
| } | ||
|
|
||
|
|
||
| class NemoCheckv2(Plugin): | ||
| """Nemo Check Adapter.""" | ||
|
|
||
| def __init__(self, config: PluginConfig): | ||
| """Entry init block for plugin. | ||
|
|
||
| Args: | ||
| logger: logger that the skill can make use of | ||
| config: the skill configuration | ||
| """ | ||
| global CHECK_ENDPOINT | ||
| logger.info(f"plugin config {config}") | ||
| endpoint = config.config.get("checkserver_url", None) | ||
| if endpoint is not None: | ||
| CHECK_ENDPOINT = endpoint | ||
| logger.info(f"checkserver at {config}:{CHECK_ENDPOINT}") | ||
| super().__init__(config) | ||
|
|
||
| async def prompt_pre_fetch( | ||
| self, payload: PromptPrehookPayload, context: PluginContext | ||
| ) -> PromptPrehookResult: | ||
| """The plugin hook run before a prompt is retrieved and rendered. | ||
|
|
||
| Args: | ||
| payload: The prompt payload to be analyzed. | ||
| context: contextual information about the hook call. | ||
|
|
||
| Returns: | ||
| The result of the plugin's analysis, including whether the prompt can proceed. | ||
| """ | ||
| return PromptPrehookResult(continue_processing=True) | ||
|
|
||
| async def prompt_post_fetch( | ||
| self, payload: PromptPosthookPayload, context: PluginContext | ||
| ) -> PromptPosthookResult: | ||
| """Plugin hook run after a prompt is rendered. | ||
|
|
||
| Args: | ||
| payload: The prompt payload to be analyzed. | ||
| context: Contextual information about the hook call. | ||
|
|
||
| Returns: | ||
| The result of the plugin's analysis, including whether the prompt can proceed. | ||
| """ | ||
| return PromptPosthookResult(continue_processing=True) | ||
|
|
||
| async def tool_pre_invoke( | ||
| self, payload: ToolPreInvokePayload, context: PluginContext | ||
| ) -> ToolPreInvokeResult: | ||
| """Plugin hook run before a tool is invoked. | ||
|
|
||
| Args: | ||
| payload: The tool payload to be analyzed. | ||
| context: Contextual information about the hook call. | ||
|
|
||
| Returns: | ||
| The result of the plugin's analysis, including whether the tool can proceed. | ||
| """ | ||
| logger.info("tool_pre_invoke....") | ||
| logger.info(payload) | ||
| tool_name = payload.name # ("tool_name", None) | ||
| check_nemo_payload = { | ||
| "model": MODEL_NAME, | ||
| "messages": [ | ||
| { | ||
| "role": "assistant", | ||
| "tool_calls": [ | ||
| { | ||
| "id": "call_plug_adap_nem_check_123", | ||
| "type": "function", | ||
| "function": { | ||
| "name": tool_name, | ||
| "arguments": payload.args.get("tool_args", None), | ||
| }, | ||
| } | ||
| ], | ||
| } | ||
| ], | ||
| } | ||
| violation = None | ||
| response = requests.post( | ||
| CHECK_ENDPOINT, headers=headers, json=check_nemo_payload | ||
| ) | ||
| if response.status_code == 200: | ||
| data = response.json() | ||
| status = data.get("status", "blocked") | ||
| logger.debug(f"rails reply:{data}") | ||
| if status == "success": | ||
| metadata = data.get("rails_status") | ||
| result = ToolPreInvokeResult( | ||
| continue_processing=True, metadata=metadata | ||
| ) | ||
| else: | ||
| metadata = data.get("rails_status") | ||
| violation = PluginViolation( | ||
| reason=f"Check tool rails:{status}.", | ||
| description=json.dumps(data), | ||
| code=f"checkserver_http_status_code:{response.status_code}", | ||
| details=metadata, | ||
| ) | ||
| result = ToolPreInvokeResult( | ||
| continue_processing=False, violation=violation, metadata=metadata | ||
| ) | ||
|
|
||
| else: | ||
| violation = PluginViolation( | ||
| reason="Tool Check Unavailable", | ||
| description="Tool arguments check server returned error:", | ||
| code=f"checkserver_http_status_code:{response.status_code}", | ||
| details={}, | ||
| ) | ||
| result = ToolPreInvokeResult(continue_processing=False, violation=violation) | ||
|
|
||
| return result | ||
|
|
||
| async def tool_post_invoke( | ||
| self, payload: ToolPostInvokePayload, context: PluginContext | ||
| ) -> ToolPostInvokeResult: | ||
| """Plugin hook run after a tool is invoked. | ||
|
|
||
| Args: | ||
| payload: The tool result payload to be analyzed. | ||
| context: Contextual information about the hook call. | ||
|
|
||
| Returns: | ||
| The result of the plugin's analysis, including whether the tool result should proceed. | ||
| """ | ||
| return ToolPostInvokeResult(continue_processing=True) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ plugins: | |
| replace: crud | ||
| - search: crud | ||
| replace: yikes | ||
|
|
||
| # Nemo example | ||
| - name: "NemoWrapperPlugin" | ||
| kind: "plugins.examples.nemo.nemo_wrapper_plugin.NemoWrapperPlugin" | ||
|
|
@@ -35,17 +36,26 @@ plugins: | |
| config: | ||
| foo: bar | ||
|
|
||
| # Nemo Check Example | ||
| - name: "NemoCheckv2" | ||
| kind: "plugins.examples.nemocheckinternal.plugin.NemoCheckv2" | ||
| description: "Adapter for nemo check server" | ||
| version: "0.1.0" | ||
| author: "Julian Stephen" | ||
| config: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the relevant hooks be present here? |
||
| checkserver_url: "http://nemo-guardrails-service:8000/v1/guardrail/checks" | ||
| # Plugin directories to scan | ||
| plugin_dirs: | ||
| - "plugins/native" # Built-in plugins | ||
| - "plugins/custom" # Custom organization plugins | ||
| - "/etc/mcpgateway/plugins" # System-wide plugins | ||
| - "plugins/examples/nemo" # Example Nemo guardrails plugins | ||
| - "plugins/examples/nemocheckinternal" # Nemo Check Server plugins | ||
|
|
||
| # Global plugin settings | ||
| plugin_settings: | ||
| parallel_execution_within_band: true | ||
| plugin_timeout: 30 | ||
| fail_on_plugin_error: false | ||
| enable_plugin_api: true | ||
| plugin_health_check_interval: 60 | ||
| plugin_health_check_interval: 60 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # plugins/config.yaml - Main plugin configuration file | ||
| plugins: | ||
| # Nemo Check Example | ||
| - name: "NemoCheckv2" | ||
| kind: "plugins.examples.nemocheckinternal.plugin.NemoCheckv2" | ||
| description: "Adapter for nemo check server" | ||
| version: "0.1.0" | ||
| author: "Julian Stephen" | ||
| hooks: ["tool_pre_invoke", "tool_post_invoke"] | ||
| tags: ["plugin", "pre-post"] | ||
| mode: "enforce" # enforce | permissive | disabled | ||
| priority: 150 | ||
| config: | ||
| checkserver_url: "http://nemo-guardrails-service:8000/v1/guardrail/checks" | ||
|
|
||
| # Plugin directories to scan | ||
| plugin_dirs: | ||
| - "plugins/native" # Built-in plugins | ||
| - "plugins/custom" # Custom organization plugins | ||
| - "/etc/mcpgateway/plugins" # System-wide plugins | ||
| - "plugins/examples/nemo" # Example Nemo guardrails plugins | ||
| - "plugins/examples/nemocheckinternal" # Nemo Check Server plugins | ||
|
|
||
| # Global plugin settings | ||
| plugin_settings: | ||
| parallel_execution_within_band: true | ||
| plugin_timeout: 30 | ||
| fail_on_plugin_error: false | ||
| enable_plugin_api: true | ||
| plugin_health_check_interval: 60 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: how distinct is the plugin implementation here vs. the external plugin itself https://github.com/kagenti/plugins-adapter/blob/main/plugins/examples/nemocheck/nemocheck/plugin.py ?
I was expecting that we would be able to use similar code but only the packaging (referencing the class path here vs. packaging up as a server for external) would be different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can, but each of the example is a bit independent and cross dependency may not be nice. We can refactor out the
call-nemo-checklogic outside both, but where do we house it? Will have to make somecommon-plugin-utilsthat needs to be an independent module that can be used in in both plugins.Another option is to simply keep the nemo internal plugin and add some packaging logic for the external one to pull the code from the internal plugin. The external one has a full project structure that can be containerized etc. Or we can simply nix the external one and fix some of the v2 naming in the internal one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I was thinking along these lines, especially as an example this seems easier to present as "internal first" rather than generally expecting the two separate servers deployed with the external plugin, but we can keep the extra packaging logic as an example, without having the code duplicated. The v2 naming is confusing if there's not fundamental plugin or API changes
side note we can probably move the
nemoguardrailsrequirement out from default requirements afterward https://github.com/kagenti/plugins-adapter/blob/main/requirements.txt