Skip to content

feat(mcp): allow deployments to override the server instructions - #1706

Open
sgral wants to merge 2 commits into
getzep:mainfrom
sgral:feat/configurable-mcp-instructions
Open

feat(mcp): allow deployments to override the server instructions#1706
sgral wants to merge 2 commits into
getzep:mainfrom
sgral:feat/configurable-mcp-instructions

Conversation

@sgral

@sgral sgral commented Jul 30, 2026

Copy link
Copy Markdown

Summary

GRAPHITI_MCP_INSTRUCTIONS is a module-level constant passed straight into FastMCP(...), so the only way to change what MCP clients are told on connect is to edit graphiti_mcp_server.py. For anyone running the server as a container that means maintaining a patched image purely to carry a few paragraphs of text.

That text is exactly where a deployment's own conventions belong — which group_ids exist, how to route facts between them, when to search before answering. The built-in default cannot know any of that, and it is the first thing every connecting client reads.

Change

A small resolve_server_instructions() helper, checked in order:

  1. GRAPHITI_MCP_INSTRUCTIONS — the instructions themselves
  2. GRAPHITI_MCP_INSTRUCTIONS_FILE — a path to read them from
  3. the built-in default (unchanged)
mcp = FastMCP(
    'Graphiti Agent Memory',
    instructions=resolve_server_instructions(),
)

Why environment and not config.yaml: the FastMCP instance is constructed at import time, before the configuration file is loaded. FastMCP.instructions is a read-only property over self._mcp_server.instructions, so setting it later in initialize_server() would mean writing to a private attribute. Resolving before construction keeps this to public API only.

Why a file path as well as an inline variable: this field is normally multi-paragraph, which is awkward to carry in an environment variable and unpleasant to quote in compose files.

Unreadable file falls back rather than raising — it logs a warning and uses the default. Wrong instructions are a much smaller problem than a memory server that will not start.

Type of Change

  • Bug fix
  • New feature
  • Performance improvement
  • Documentation/Tests

Objective

Let deployments describe their own memory conventions without forking the image. This came out of running the MCP server against FalkorDB with several active group_ids, where the routing rules an agent needs are entirely site-specific.

Testing

  • Unit tests added — mcp_server/tests/test_server_instructions.py, five cases: default, inline override, file override, inline-beats-file precedence, and unreadable-file fallback.
  • Integration tests added/updated
  • Existing tests unaffected — the change is additive and the default path is byte-identical to before.

Note: I verified the resolution order by exercising the function's logic directly (all five cases pass) but could not run the repository's pytest suite in my environment. Worth a CI run.

Breaking Changes

  • This PR contains breaking changes

With neither variable set, behaviour is exactly as before.

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated where necessary — both variables added to the environment table in mcp_server/README.md
  • No secrets or sensitive information committed

GRAPHITI_MCP_INSTRUCTIONS is a module-level constant passed straight into
FastMCP, so the only way to change what clients are told on connect is to edit
graphiti_mcp_server.py — which means maintaining a patched image just to carry
a few paragraphs of text.

That text is exactly where a deployment's own conventions belong: which
group_ids exist, how to route facts between them, when to search before
answering. The built-in default cannot know any of that.

resolve_server_instructions() now checks, in order:

  1. GRAPHITI_MCP_INSTRUCTIONS       — the instructions themselves
  2. GRAPHITI_MCP_INSTRUCTIONS_FILE  — a path to read them from
  3. the built-in default

Environment rather than config.yaml because the FastMCP instance is built at
import time, before the config file is loaded. A file path is offered alongside
the inline variable because this field is normally multi-paragraph, which is
awkward to carry in an env var.

An unreadable file logs a warning and falls back to the default rather than
raising: bad instructions are a much smaller problem than a memory server that
will not start.

Fully backward compatible — with neither variable set the built-in text is used
and nothing changes.
@zep-cla-assistant

Copy link
Copy Markdown
Contributor


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. For privacy information, see our Privacy Notice. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA behalf on myself, e-mail: example@example.com

or

I have read the CLA Document and I hereby sign the CLA behalf of my company, e-mail: example@example.com

Signature is valid for 6 months.


This bot will be retriggered when the Contributor License Agreement comment has been provided. Posted by the CLA Assistant Lite bot.

CI caught two things I should have run locally before opening the PR:

  F401  'os' imported but unused in the new test module — the fixture uses
        monkeypatch rather than os.environ directly, so the import was dead
  format  a missing blank line in graphiti_mcp_server.py

Verified with the exact pinned version the workflow uses (ruff==0.14.11):
'ruff check' and 'ruff format --check' both clean repo-wide (266 files).
@sgral

sgral commented Jul 30, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA behalf on myself, e-mail: sebastian.gral96@gmail.com

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.

1 participant