Skip to content

feat: Add new toolset for OpenSearch Agentic Memory API#138

Open
oujezdsky wants to merge 31 commits intoopensearch-project:mainfrom
oujezdsky:feature/113-add-memory-tools
Open

feat: Add new toolset for OpenSearch Agentic Memory API#138
oujezdsky wants to merge 31 commits intoopensearch-project:mainfrom
oujezdsky:feature/113-add-memory-tools

Conversation

@oujezdsky
Copy link
Copy Markdown

Description

This pull request introduces a set of 7 new tools to support the OpenSearch Agentic Memory API (released in 3.3). This allows agents using the MCP server to create, manage, and interact with agentic memories.

Key changes include:

  • New Tools: Adds CreateAgenticMemoryContainerTool, CreateAgenticMemorySessionTool, AddAgenticMemoriesTool, GetAgenticMemoryTool, UpdateAgenticMemoryArgsTool, DeleteAgenticMemoryByIDTool, DeleteAgenticMemoryByQueryTool, and SearchAgenticMemoryTool.
  • Pydantic Models: Implements Pydantic V2 models for all new tool arguments.
  • Async Implementation: The core logic is implemented as async helper functions in src/opensearch/helper.py.
  • Error Handling: Introduces a new HelperOperationError to provide better, contextualized error logging.
  • Testing: Adds a set of unit tests for the new tools, using pytest.mark.parametrize for all happy paths and pytest.raises for Pydantic validation.

Issues Resolved

Closes #113

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
…uster_name param

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
@oujezdsky oujezdsky marked this pull request as draft November 18, 2025 14:43
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
…Tool

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
…moryTool

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
@oujezdsky oujezdsky marked this pull request as ready for review November 18, 2025 19:05
@oujezdsky
Copy link
Copy Markdown
Author

Hello,

This PR is ready for review again.

5 new commits that address issues:

  • Fixed packaging by moving exceptions.py.
  • Added compatibility checks to all Agentic Memory tools.
  • Corrected naming for UpdateAgenticMemoryTool (in code and CHANGELOG).
  • Fixed CreateAgenticMemoryContainerTool validation by changing the strategy field from invalid strategy_type to the correct type.

Thank you for the review and apologies for the inconvenience.

@rithin-pullela-aws
Copy link
Copy Markdown
Collaborator

Hi @oujezdsky thanks for the PR!

Few high level thoughts before I review the PR:

  • Can we fix the failing tests?
  • Can we add the tools in Readme and Userguide?
  • Looks like the tool_params.py and tool.py have a lot of lines of code added. Can we create separate files for agentic_memory related tools and import the tools into tools.py ? I believe this would make the code more readable

@oujezdsky
Copy link
Copy Markdown
Author

oujezdsky commented Nov 18, 2025

Hello @rithin-pullela-aws,
Yes, I will look at the failed tests (seemed OK in my dev env.) and at the separation of agentic_memory tools.

Regarding the Readme and Userguide—no problem, I’ll add the new tools there as well.
I originally thought it was outside the scope of this issue, and I had a similar assumption about adding the new AM tools to src/tools/tool_filter.py, specifically inside process_tool_filter() function and its core_tools. Without that, opensearch-mcp-server-py won’t detect them. Should I add them there as well?

And if so, would you prefer that I include them directly in core_tools, or should I create a separate group like agentic_memory_tools and follow the same pattern used for core_tools?

thanks

@oujezdsky oujezdsky marked this pull request as draft November 18, 2025 20:10
…Tool

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
@oujezdsky
Copy link
Copy Markdown
Author

This PR has been updated to address all comments and is now ready for re-review.

  1. Refactoring and Readability

    • Solution: All Agentic Memory implementation logic (functions, Pydantic models, constants) has been extracted from the large files (tools.py and tool_params.py) into a new, dedicated Python package: src/tools/agentic_memory/.

    • This package now segregates logic into actions.py and data models into params.py.

  2. Fixing Failing Tests (Version Compatibility)

    • Problem: The new tools required OpenSearch 3.3.0+, but the existing test class (TestTools) was mocked to version 2.19.0 in its setup_method. The check_tool_compatibility function correctly raised an incompatibility error, causing all Agentic Memory tests to fail.

    • Solution: To fix this without affecting the current test environment, I have isolated all Agentic Memory tests into a new file: tests/tools/test_agentic_memory_tools.py. The test class within this file now defines its own isolated setup_method, mocking the OpenSearch version as 3.3.0. This ensures the new feature is tested against the correct version while preserving the existing test baseline.

  3. Documentation Updates (README & User Guide)

    • README.md: Updated the "Available Tools" section, adding the full list of Agentic Memory tools and their parameter definitions.

    • USER_GUIDE.md: Added a new chapter "Agentic Memory Usage" with a detailed 5-step workflow (Create, Session, Add, Search, Update/Delete) using the "Vacation Planner" example.

@oujezdsky oujezdsky marked this pull request as ready for review November 19, 2025 12:36
Copy link
Copy Markdown
Collaborator

@nathaliellenaa nathaliellenaa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for raising this PR @oujezdsky! Added some comments

CHANGELOG.md Outdated

### Fixed
- Fix AWS auth issues for cat based tools, pin OpenSearchPy to 2.18.0 ([#135](https://github.com/opensearch-project/opensearch-mcp-server-py/pull/135))
- Add new toolset for the OpenSearch Agentic Memory API: `CreateAgenticMemoryContainerTool`, `CreateAgenticMemorySessionTool`, `AddAgenticMemoriesTool`, `GetAgenticMemoryTool`, `UpdateAgenticMemoryTool`, `DeleteAgenticMemoryByIDTool`, `DeleteAgenticMemoryByQueryTool`, and `SearchAgenticMemoryTool`. ([#113](https://github.com/opensearch-project/opensearch-mcp-server-py/pull/138))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Add new toolset for the OpenSearch Agentic Memory API: `CreateAgenticMemoryContainerTool`, `CreateAgenticMemorySessionTool`, `AddAgenticMemoriesTool`, `GetAgenticMemoryTool`, `UpdateAgenticMemoryTool`, `DeleteAgenticMemoryByIDTool`, `DeleteAgenticMemoryByQueryTool`, and `SearchAgenticMemoryTool`. ([#113](https://github.com/opensearch-project/opensearch-mcp-server-py/pull/138))
- Add new toolset for the OpenSearch Agentic Memory API: `CreateAgenticMemoryContainerTool`, `CreateAgenticMemorySessionTool`, `AddAgenticMemoriesTool`, `GetAgenticMemoryTool`, `UpdateAgenticMemoryTool`, `DeleteAgenticMemoryByIDTool`, `DeleteAgenticMemoryByQueryTool`, and `SearchAgenticMemoryTool`. ([#138](https://github.com/opensearch-project/opensearch-mcp-server-py/pull/138))

README.md Outdated
- `messages` (conditional): A list of messages. Required when `payload_type` is `conversational`. *(Body Parameter)*
- `structured_data` (conditional): Structured data content. Required when `payload_type` is `data`. *(Body Parameter)*
- `binary_data` (optional): Binary data content encoded as a Base64 string for binary payloads. *(Body Parameter)*
- `payload_type` (required): The type of payload. Valid values are `conversational` or `data`. See [See Payload types](https://docs.opensearch.org/latest/ml-commons-plugin/agentic-memory/#payload-types). *(Body Parameter)*
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `payload_type` (required): The type of payload. Valid values are `conversational` or `data`. See [See Payload types](https://docs.opensearch.org/latest/ml-commons-plugin/agentic-memory/#payload-types). *(Body Parameter)*
- `payload_type` (required): The type of payload. Valid values are `conversational` or `data`. See [Payload types](https://docs.opensearch.org/latest/ml-commons-plugin/agentic-memory/#payload-types). *(Body Parameter)*

README.md Outdated
- `namespace` (optional): The [namespace](https://docs.opensearch.org/latest/ml-commons-plugin/agentic-memory/#namespaces) context for organizing memories (for example, `user_id`, `session_id`, or `agent_id`). If `session_id` is not specified in the namespace field and `disable_session`: `false` (default is `true`), a new session with a new session ID is created. *(Body Parameter)*
- `metadata` (optional): Additional metadata for the memory (for example, `status`, `branch`, or custom fields). *(Body Parameter)*
- `tags` (optional): Tags for categorizing memories. *(Body Parameter)*
- `infer` (optional): Whether to use an LLM to extract key information (default: `false`). When `true`, the LLM extracts key information from the original text and stores it as a memory. [See Inference mode](https://docs.opensearch.org/latest/ml-commons-plugin/agentic-memory/#inference-mode). *(Body Parameter)*
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `infer` (optional): Whether to use an LLM to extract key information (default: `false`). When `true`, the LLM extracts key information from the original text and stores it as a memory. [See Inference mode](https://docs.opensearch.org/latest/ml-commons-plugin/agentic-memory/#inference-mode). *(Body Parameter)*
- `infer` (optional): Whether to use an LLM to extract key information (default: `false`). When `true`, the LLM extracts key information from the original text and stores it as a memory. See [Inference mode](https://docs.opensearch.org/latest/ml-commons-plugin/agentic-memory/#inference-mode). *(Body Parameter)*

"""Create a new agentic memory session in the specified memory container.

Args:
args: CreateSessionArgs containing memory_container_id and optional session_id, summary, metadata, namespace
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
args: CreateSessionArgs containing memory_container_id and optional session_id, summary, metadata, namespace
args: CreateAgenticMemorySessionArgs containing memory_container_id and optional session_id, summary, metadata, namespace

return [{'type': 'text', 'text': f'Error updating memory: {str(error_to_report)}'}]


async def delete_agentic_memoryby_ID_tool(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function name be should be delete_agentic_memory_by_id_tool to make it consistent with other function names

Comment on lines +43 to +48
if container_id:
message = f'Successfully created memory container. ID: {container_id}. Response: {json.dumps(result)}'
else:
message = (
f'Memory container created, but no ID was returned. Response: {json.dumps(result)}'
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep a consistent message formatting? We can follow the format in create_agentic_memory_session_tool, for example

message = (
            f'Successfully created memory container. ID: {container_id}. Response: {json.dumps(result)}'
            if container_id
            else f'Memory container created, but no ID was returned. Response: {json.dumps(result)}'
)

@model_validator(mode='after')
def validate_embedding_configuration(self) -> 'AgenticMemoryConfigurationArgs':
"""Validate embedding model configuration."""
if self.embedding_model_type == 'TEXT_EMBEDDING' and self.embedding_dimension is None:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do if self.embedding_model_type == EmbeddingModelType.text_embedding?

'function': create_agentic_memory_session_tool,
'args_model': CreateAgenticMemorySessionArgs,
'min_version': '3.3.0', # Agentic memory APIs requires OpenSearch 3.3+
'http_methods': 'UPDATE',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The http_methods should be the actual REST API method being called, so in this case it should be POST. The same should be applied for the other agentic memory tools

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
…tency

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
@oujezdsky
Copy link
Copy Markdown
Author

@nathaliellenaa Thanks for the feedback! I've addressed all the comments.

@jiapingzeng
Copy link
Copy Markdown
Contributor

Thanks for the PR! Overall the changes look good to me from a tools perspective.

One question is that when using these MCP tools with an agent, e.g. Claude desktop or Q/Kiro CLI, is it able to recognize that it needs to create a container before it can use the other Agentic Memory tools? Or would the user need to guide it through prompt to create container then add/get/update/delete memory?

@oujezdsky
Copy link
Copy Markdown
Author

oujezdsky commented Nov 25, 2025

Thanks for the feedback!

Regarding the MCP tools themselves, it relies on the tool definitions (schemas). For example, since AddAgenticMemoriesTool requires a memory_container_id, the user should deduce that the CreateAgenticMemoryContainerTool must be called first in order to obtain the memory_container_id.

But I could extend the description in BaseAgenticMemoryContainerArgs, where the mandatory field memory_container_id is specified.
(BaseAgenticMemoryContainerArgs is used only within add, create session, get, update, delete, and search...)"

For example, like this:

class BaseAgenticMemoryContainerArgs(baseToolArgs):
    """Base arguments for tools operating on an existing Agentic Memory Container."""

    memory_container_id: str = Field(
        ..., 
        description='The ID of the memory container. You must obtain this ID by calling CreateAgenticMemoryContainerTool first.'
    )

(snippet from src/tools/agentic_memory/params.py)

…ory-tools

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
@dhrubo-os
Copy link
Copy Markdown
Contributor

Thanks for the feedback!

Regarding the MCP tools themselves, it relies on the tool definitions (schemas). For example, since AddAgenticMemoriesTool requires a memory_container_id, the user should deduce that the CreateAgenticMemoryContainerTool must be called first in order to obtain the memory_container_id.

But I could extend the description in BaseAgenticMemoryContainerArgs, where the mandatory field memory_container_id is specified. (BaseAgenticMemoryContainerArgs is used only within add, create session, get, update, delete, and search...)"

For example, like this:

class BaseAgenticMemoryContainerArgs(baseToolArgs):
    """Base arguments for tools operating on an existing Agentic Memory Container."""

    memory_container_id: str = Field(
        ..., 
        description='The ID of the memory container. You must obtain this ID by calling CreateAgenticMemoryContainerTool first.'
    )

(snippet from src/tools/agentic_memory/params.py)

I think we should keep container creation separate from the MCP tools for these reasons:

  1. Container creation is infrastructure setup - It requires careful configuration of embedding models, LLM connectors, strategies, and index settings. This is typically a one-time admin operation, not something
    agents should do at runtime.

  2. Runtime operations need pre-configured containers - For the actual memory operations (add, search, update, delete), agents should use a pre-existing memory_container_id.

  3. MCP server already supports this pattern - Users can pre-configure the memory_container_id in the tool schema:

 tools:
   AddAgenticMemoriesTool:
     args:
       memory_container_id: "your-container-id-here"

This way, agents don't need to pass it with every call.

@jiapingzeng
Copy link
Copy Markdown
Contributor

I think we should keep container creation separate from the MCP tools for these reasons

+1 on we should keep memory container creation separate as otherwise agent will create a new container each time and won't be able to access previous session's memory.

Though for ease-of-use purpose we should consider if we can provide an easy way to create the container either via a script or via optional MCP tools. Having to manually setup embedding models, LLM connectors, strategies, index settings manually could be overwhelming for new users of Agentic Memory.

Users can pre-configure the memory_container_id in the tool schema

Yes container ID can be provided via tool customization, maybe we can also allow it to be configured via env vars.

I think memory tools should also be disabled by default (so that agents don't try to invoke them without a container ready), and users who do want to use memory can specify flag to enable and provide memory container ID.

@dhrubo-os
Copy link
Copy Markdown
Contributor

Though for ease-of-use purpose we should consider if we can provide an easy way to create the container either via a script or via optional MCP tools. Having to manually setup embedding models, LLM connectors, strategies, index settings manually could be overwhelming for new users of Agentic Memory.

Yeah +1 on this. We can take that as a follow up item.

I think memory tools should also be disabled by default (so that agents don't try to invoke them without a container ready), and users who do want to use memory can specify flag to enable and provide memory container ID.

Agree with disabling by default. To make this seamless, we could auto-enable based on configuration:

# Only register memory tools if container ID is configured
if os.getenv('OPENSEARCH_MEMORY_CONTAINER_ID'):
   TOOL_REGISTRY.update({...memory tools...})

This way:

  • No container ID → tools not visible to agents (no failed invocations)
  • Container ID set → tools automatically available
  • Zero manual enable/disable flags needed

Users just set OPENSEARCH_MEMORY_CONTAINER_ID env var and memory tools appear automatically.

@nathaliellenaa
Copy link
Copy Markdown
Collaborator

I think we should keep container creation separate from the MCP tools for these reasons

+1 on we should keep memory container creation separate as otherwise agent will create a new container each time and won't be able to access previous session's memory.

Though for ease-of-use purpose we should consider if we can provide an easy way to create the container either via a script or via optional MCP tools. Having to manually setup embedding models, LLM connectors, strategies, index settings manually could be overwhelming for new users of Agentic Memory.

I agree on separating the memory container creation from the MCP tools as the configuration can be complex and it's error prone for agents to handle at runtime.

To make the initial setup easier, we could support pre-built container templates (e.g., default, minimal, advanced). This way, new users who aren't familiar with Agentic Memory can simply run python -m mcp_server_opensearch --container_template default. This would create a container with sensible defaults, and more advanced users can still customize their own setup

Users can pre-configure the memory_container_id in the tool schema

Yes container ID can be provided via tool customization, maybe we can also allow it to be configured via env vars.

I think memory tools should also be disabled by default (so that agents don't try to invoke them without a container ready), and users who do want to use memory can specify flag to enable and provide memory container ID.

+1 on this. I think we can introduce a dedicated flag for the agentic memory feature. When users enable this flag, all agentic memory-related tools will be automatically enabled, and they can provide the memory_container_id at server startup. For example python -m mcp_server_opensearch --agentic_memory --memory_container_id <id>

@dhrubo-os
Copy link
Copy Markdown
Contributor

@oujezdsky if possible can we target to make the suggested changes and merge this PR asap? Thanks a lot for working on this.

@oujezdsky
Copy link
Copy Markdown
Author

Hi everyone, thanks for the detailed feedback.

@dhrubo-os I'll be able to work on it this weekend. There's a chance I'll be able to do it sooner, but the weekend is the most certain.
I'll push the updates as soon as I have them ready. Thanks

…ory-tools

Signed-off-by: Jiri Oujezdsky <oujezdsky2@gmail.com>
@oujezdsky
Copy link
Copy Markdown
Author

@dhrubo-os Quick question regarding YAML config.

I checked how tools.<Tool>.args is parsed/applied in config.py: right now it looks like args.<arg>: "<string>" is used only to override the argument description, not as a runtime default value. Am I reading that correctly?
Code I am talking about is here and here.

If so, are you OK with me extending the config format to support defaults, e.g.:

tools:
  ListIndexTool:
    display_name: "My_Custom_Index_Lister"
    description: "This is a custom description for the tool that lists indices. It's much more descriptive now!"
    args:
      index: "Custom description for the 'index' argument in ListIndexTool."
  AddAgenticMemoriesTool:
    args:
      memory_container_id:
        default: "your-container-id"

so the server can prefill memory_container_id from config and agents don’t need to pass it on every call?

I would implement it in such a way that it does not affect the existing functionality around parsing tools.<Tool>.args

@dhrubo-os
Copy link
Copy Markdown
Contributor

@dhrubo-os Quick question regarding YAML config.

I checked how tools.<Tool>.args is parsed/applied in config.py: right now it looks like args.<arg>: "<string>" is used only to override the argument description, not as a runtime default value. Am I reading that correctly? Code I am talking about is here and here.

If so, are you OK with me extending the config format to support defaults, e.g.:

tools:
  ListIndexTool:
    display_name: "My_Custom_Index_Lister"
    description: "This is a custom description for the tool that lists indices. It's much more descriptive now!"
    args:
      index: "Custom description for the 'index' argument in ListIndexTool."
  AddAgenticMemoriesTool:
    args:
      memory_container_id:
        default: "your-container-id"

so the server can prefill memory_container_id from config and agents don’t need to pass it on every call?

I would implement it in such a way that it does not affect the existing functionality around parsing tools.<Tool>.args

Sorry for the late reply. Sure go ahead. I prefer that too.

@oujezdsky
Copy link
Copy Markdown
Author

Okay

@dhrubo-os
Copy link
Copy Markdown
Contributor

Okay

LEt's target to merge this PR by this month if possible. Also how did we test if the tools are correctly picking up or not? Specially adding memory and search memory?

@oujezdsky
Copy link
Copy Markdown
Author

Okay

LEt's target to merge this PR by this month if possible. Also how did we test if the tools are correctly picking up or not? Specially adding memory and search memory?

I tested all CRUD + search operatios. Everything was OK.
PR by this month should not be a problem, I will be working on it in the next days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add memory tools

5 participants