plain-text-memory-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. plain_text_memory_mcp-0.1.0/LICENSE +21 -0
  2. plain_text_memory_mcp-0.1.0/PKG-INFO +215 -0
  3. plain_text_memory_mcp-0.1.0/README.md +192 -0
  4. plain_text_memory_mcp-0.1.0/pyproject.toml +73 -0
  5. plain_text_memory_mcp-0.1.0/setup.cfg +4 -0
  6. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/__init__.py +1 -0
  7. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/agent.py +24 -0
  8. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/graph.py +320 -0
  9. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/prompts/cleanup.md +37 -0
  10. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/prompts/export.md +20 -0
  11. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/records.py +233 -0
  12. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/server.py +753 -0
  13. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/storage.py +312 -0
  14. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp/taxonomy.json +503 -0
  15. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp.egg-info/PKG-INFO +215 -0
  16. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp.egg-info/SOURCES.txt +28 -0
  17. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp.egg-info/dependency_links.txt +1 -0
  18. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp.egg-info/entry_points.txt +2 -0
  19. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp.egg-info/requires.txt +1 -0
  20. plain_text_memory_mcp-0.1.0/src/plain_text_memory_mcp.egg-info/top_level.txt +1 -0
  21. plain_text_memory_mcp-0.1.0/tests/test_agent.py +32 -0
  22. plain_text_memory_mcp-0.1.0/tests/test_export.py +157 -0
  23. plain_text_memory_mcp-0.1.0/tests/test_graph.py +172 -0
  24. plain_text_memory_mcp-0.1.0/tests/test_header.py +97 -0
  25. plain_text_memory_mcp-0.1.0/tests/test_records.py +110 -0
  26. plain_text_memory_mcp-0.1.0/tests/test_repo_guard.py +165 -0
  27. plain_text_memory_mcp-0.1.0/tests/test_server.py +566 -0
  28. plain_text_memory_mcp-0.1.0/tests/test_stdio.py +93 -0
  29. plain_text_memory_mcp-0.1.0/tests/test_storage.py +218 -0
  30. plain_text_memory_mcp-0.1.0/tests/test_taxonomy.py +230 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Plain Text Office LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: plain-text-memory-mcp
3
+ Version: 0.1.0
4
+ Summary: Knowledge-graph memory MCP server that tags entries with date and agent
5
+ Author: Plain Text Office LLC
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/PlainTxtOffice/plain-text-memory-mcp
8
+ Project-URL: Issues, https://github.com/PlainTxtOffice/plain-text-memory-mcp/issues
9
+ Keywords: mcp,model-context-protocol,memory,knowledge-graph,claude-code,codex
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: mcp<3,>=2.2
22
+ Dynamic: license-file
23
+
24
+ # plain-text-memory-mcp
25
+
26
+ Knowledge-graph memory MCP server that tags every entry with the date it was
27
+ added and the agent that added it. Memory lives in a plain JSONL file inside
28
+ each repo, so every agent working in that repo shares it.
29
+
30
+ It is a drop-in replacement for `@modelcontextprotocol/server-memory`: the
31
+ same nine tools and input keys, and it reads files written by the original
32
+ server. It adds `initialize_memory` and `export_taxonomy`, the `cleanup` and
33
+ `export` prompts, and the `memory://taxonomy` resource.
34
+
35
+ ## Install
36
+
37
+ The server runs with [uv](https://docs.astral.sh/uv/); `uvx` downloads and
38
+ starts it on demand, so there is nothing else to install.
39
+
40
+ ```shell
41
+ uvx plain-text-memory-mcp
42
+ ```
43
+
44
+ Started by hand, it waits for an MCP client on stdin; press Ctrl+C to stop.
45
+
46
+ ## Configure
47
+
48
+ Add the server to each agent's MCP config. The server finds the repo from the
49
+ folder the agent starts it in, so register it per project, or set
50
+ `MEMORY_FILE_PATH` if a client starts servers somewhere else.
51
+
52
+ Claude Code, `.mcp.json` in the repo:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "plain-text-memory": {
58
+ "type": "stdio",
59
+ "command": "uvx",
60
+ "args": ["plain-text-memory-mcp"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ Codex, `.codex/config.toml`:
67
+
68
+ ```toml
69
+ [mcp_servers.plain-text-memory]
70
+ command = "uvx"
71
+ args = ["plain-text-memory-mcp"]
72
+ ```
73
+
74
+ Cursor, `.cursor/mcp.json` in the repo:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "plain-text-memory": {
80
+ "command": "uvx",
81
+ "args": ["plain-text-memory-mcp"]
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ VS Code, `.vscode/mcp.json` in the repo:
88
+
89
+ ```json
90
+ {
91
+ "servers": {
92
+ "plain-text-memory": {
93
+ "type": "stdio",
94
+ "command": "uvx",
95
+ "args": ["plain-text-memory-mcp"]
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Where memory is stored
102
+
103
+ 1. `MEMORY_FILE_PATH`, if set. A relative path resolves against the folder
104
+ the agent starts the server in.
105
+ 1. `<git root>/.agents/memory.local.jsonl`.
106
+ 1. `<start folder>/.agents/memory.local.jsonl` outside a git repo.
107
+
108
+ Writes hold a lock on `memory.local.jsonl.lock` beside the memory file, so
109
+ Claude Code and Codex can write to the same repo's memory at the same time
110
+ without losing each other's changes. The file stays after the server exits;
111
+ leave it in place.
112
+
113
+ ## Tags
114
+
115
+ Every entity, relation, and observation carries `added_at` (local time with
116
+ UTC offset) and `agent` (`claude-code`, `codex`, or the client's own name).
117
+ Set `MEMORY_AGENT` to override the agent name. Entries written by the original
118
+ server show `null` for both.
119
+
120
+ ## Repo guard
121
+
122
+ The first write adds a header line recording which repo the memory file
123
+ belongs to. If the file is later found in a different repo, for example after
124
+ copying `.agents/` from a template, every tool returns an error instead of
125
+ serving the other repo's memories. Resolve it with the `initialize_memory`
126
+ tool:
127
+
128
+ - `mode: "fresh"` renames the old file to `memory.local.jsonl.bak-<time>` and
129
+ starts an empty graph.
130
+ - `mode: "adopt"` keeps the memories and points the header at this repo, for
131
+ a repo that was moved or renamed.
132
+
133
+ Files without a header, including ones written by the original server, are
134
+ claimed by the repo that writes to them first.
135
+
136
+ ## Prompts
137
+
138
+ The server publishes two MCP prompts, which clients offer as commands; Claude
139
+ Code shows them as `/mcp__plain-text-memory__cleanup` and
140
+ `/mcp__plain-text-memory__export`.
141
+
142
+ | Prompt | Use |
143
+ | --- | --- |
144
+ | `cleanup` | Propose deletions and merges; nothing is deleted until approved |
145
+ | `export` | Write the repo's taxonomy and usage to `.agents/` and report it |
146
+
147
+ ## Guidance for agents
148
+
149
+ On connect, the server sends every MCP client instructions for using memory
150
+ well: search before planning, verify what memory says against the repo, save
151
+ only lasting facts, and resolve the repo guard. Tool and parameter
152
+ descriptions say what each tool does and returns, and the prompts above
153
+ carry the cleanup and export workflows. No agent-specific setup is needed.
154
+
155
+ ## Taxonomy
156
+
157
+ [taxonomy.json](src/plain_text_memory_mcp/taxonomy.json) lists every record
158
+ type with its purpose and fields, every tool with its parameters and results,
159
+ every MCP resource and prompt, and the allowed values for enum fields such
160
+ as `agent` and `initialize_memory.mode`. It is generated from the code; do
161
+ not edit it by hand. Record type purposes live in `RECORD_PURPOSES`
162
+ in `records.py`. The file ships inside the package, where the server's
163
+ `export_taxonomy` tool reads it. MCP clients can also read it as the
164
+ resource `memory://taxonomy`.
165
+
166
+ `taxonomy_version` follows semantic versioning and bumps itself: a removed
167
+ item, section, or changed type is major, an added item is minor, and a
168
+ description, purpose, title, or package version change is patch. Each bump
169
+ adds a `changelog` entry listing what changed.
170
+
171
+ Regenerate it with:
172
+
173
+ ```powershell
174
+ .venv/Scripts/python.exe scripts/build_taxonomy.py
175
+ ```
176
+
177
+ The pre-commit hook runs this and stops the commit when the file changes, so
178
+ the new version is reviewed and staged. A test also fails when the committed
179
+ file is stale. Enable the hook once per clone:
180
+
181
+ ```powershell
182
+ git config core.hooksPath .githooks
183
+ ```
184
+
185
+ ### Exporting to a repo
186
+
187
+ Ask an agent to call the `export_taxonomy` tool. It writes
188
+ `.agents/memory.local.taxonomy.jsonc` beside the repo's memory file, holding
189
+ the versioned schema above plus this repo's usage: entry counts, the entity
190
+ and relation types in use, and entries per agent. `//` comments explain each
191
+ section and each record type's purpose. Use it to keep type names consistent
192
+ within a repo. The export is local, like the memory file; ignore both with
193
+ `.agents/*memory.local.*`.
194
+
195
+ ## Develop
196
+
197
+ ```powershell
198
+ py -3.13 -m venv .venv
199
+ .venv/Scripts/python.exe -m pip install -e . --group dev
200
+ .venv/Scripts/python.exe -m pytest
201
+ ```
202
+
203
+ To have agents run your working copy instead of the published package,
204
+ install it as an editable tool and use `plain-text-memory-mcp` as the
205
+ config's command, with no args:
206
+
207
+ ```powershell
208
+ uv tool install -e .
209
+ ```
210
+
211
+ Code changes then take effect the next time an agent starts the server.
212
+
213
+ ## License
214
+
215
+ MIT, copyright Plain Text Office LLC. See [LICENSE](LICENSE).
@@ -0,0 +1,192 @@
1
+ # plain-text-memory-mcp
2
+
3
+ Knowledge-graph memory MCP server that tags every entry with the date it was
4
+ added and the agent that added it. Memory lives in a plain JSONL file inside
5
+ each repo, so every agent working in that repo shares it.
6
+
7
+ It is a drop-in replacement for `@modelcontextprotocol/server-memory`: the
8
+ same nine tools and input keys, and it reads files written by the original
9
+ server. It adds `initialize_memory` and `export_taxonomy`, the `cleanup` and
10
+ `export` prompts, and the `memory://taxonomy` resource.
11
+
12
+ ## Install
13
+
14
+ The server runs with [uv](https://docs.astral.sh/uv/); `uvx` downloads and
15
+ starts it on demand, so there is nothing else to install.
16
+
17
+ ```shell
18
+ uvx plain-text-memory-mcp
19
+ ```
20
+
21
+ Started by hand, it waits for an MCP client on stdin; press Ctrl+C to stop.
22
+
23
+ ## Configure
24
+
25
+ Add the server to each agent's MCP config. The server finds the repo from the
26
+ folder the agent starts it in, so register it per project, or set
27
+ `MEMORY_FILE_PATH` if a client starts servers somewhere else.
28
+
29
+ Claude Code, `.mcp.json` in the repo:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "plain-text-memory": {
35
+ "type": "stdio",
36
+ "command": "uvx",
37
+ "args": ["plain-text-memory-mcp"]
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ Codex, `.codex/config.toml`:
44
+
45
+ ```toml
46
+ [mcp_servers.plain-text-memory]
47
+ command = "uvx"
48
+ args = ["plain-text-memory-mcp"]
49
+ ```
50
+
51
+ Cursor, `.cursor/mcp.json` in the repo:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "plain-text-memory": {
57
+ "command": "uvx",
58
+ "args": ["plain-text-memory-mcp"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ VS Code, `.vscode/mcp.json` in the repo:
65
+
66
+ ```json
67
+ {
68
+ "servers": {
69
+ "plain-text-memory": {
70
+ "type": "stdio",
71
+ "command": "uvx",
72
+ "args": ["plain-text-memory-mcp"]
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## Where memory is stored
79
+
80
+ 1. `MEMORY_FILE_PATH`, if set. A relative path resolves against the folder
81
+ the agent starts the server in.
82
+ 1. `<git root>/.agents/memory.local.jsonl`.
83
+ 1. `<start folder>/.agents/memory.local.jsonl` outside a git repo.
84
+
85
+ Writes hold a lock on `memory.local.jsonl.lock` beside the memory file, so
86
+ Claude Code and Codex can write to the same repo's memory at the same time
87
+ without losing each other's changes. The file stays after the server exits;
88
+ leave it in place.
89
+
90
+ ## Tags
91
+
92
+ Every entity, relation, and observation carries `added_at` (local time with
93
+ UTC offset) and `agent` (`claude-code`, `codex`, or the client's own name).
94
+ Set `MEMORY_AGENT` to override the agent name. Entries written by the original
95
+ server show `null` for both.
96
+
97
+ ## Repo guard
98
+
99
+ The first write adds a header line recording which repo the memory file
100
+ belongs to. If the file is later found in a different repo, for example after
101
+ copying `.agents/` from a template, every tool returns an error instead of
102
+ serving the other repo's memories. Resolve it with the `initialize_memory`
103
+ tool:
104
+
105
+ - `mode: "fresh"` renames the old file to `memory.local.jsonl.bak-<time>` and
106
+ starts an empty graph.
107
+ - `mode: "adopt"` keeps the memories and points the header at this repo, for
108
+ a repo that was moved or renamed.
109
+
110
+ Files without a header, including ones written by the original server, are
111
+ claimed by the repo that writes to them first.
112
+
113
+ ## Prompts
114
+
115
+ The server publishes two MCP prompts, which clients offer as commands; Claude
116
+ Code shows them as `/mcp__plain-text-memory__cleanup` and
117
+ `/mcp__plain-text-memory__export`.
118
+
119
+ | Prompt | Use |
120
+ | --- | --- |
121
+ | `cleanup` | Propose deletions and merges; nothing is deleted until approved |
122
+ | `export` | Write the repo's taxonomy and usage to `.agents/` and report it |
123
+
124
+ ## Guidance for agents
125
+
126
+ On connect, the server sends every MCP client instructions for using memory
127
+ well: search before planning, verify what memory says against the repo, save
128
+ only lasting facts, and resolve the repo guard. Tool and parameter
129
+ descriptions say what each tool does and returns, and the prompts above
130
+ carry the cleanup and export workflows. No agent-specific setup is needed.
131
+
132
+ ## Taxonomy
133
+
134
+ [taxonomy.json](src/plain_text_memory_mcp/taxonomy.json) lists every record
135
+ type with its purpose and fields, every tool with its parameters and results,
136
+ every MCP resource and prompt, and the allowed values for enum fields such
137
+ as `agent` and `initialize_memory.mode`. It is generated from the code; do
138
+ not edit it by hand. Record type purposes live in `RECORD_PURPOSES`
139
+ in `records.py`. The file ships inside the package, where the server's
140
+ `export_taxonomy` tool reads it. MCP clients can also read it as the
141
+ resource `memory://taxonomy`.
142
+
143
+ `taxonomy_version` follows semantic versioning and bumps itself: a removed
144
+ item, section, or changed type is major, an added item is minor, and a
145
+ description, purpose, title, or package version change is patch. Each bump
146
+ adds a `changelog` entry listing what changed.
147
+
148
+ Regenerate it with:
149
+
150
+ ```powershell
151
+ .venv/Scripts/python.exe scripts/build_taxonomy.py
152
+ ```
153
+
154
+ The pre-commit hook runs this and stops the commit when the file changes, so
155
+ the new version is reviewed and staged. A test also fails when the committed
156
+ file is stale. Enable the hook once per clone:
157
+
158
+ ```powershell
159
+ git config core.hooksPath .githooks
160
+ ```
161
+
162
+ ### Exporting to a repo
163
+
164
+ Ask an agent to call the `export_taxonomy` tool. It writes
165
+ `.agents/memory.local.taxonomy.jsonc` beside the repo's memory file, holding
166
+ the versioned schema above plus this repo's usage: entry counts, the entity
167
+ and relation types in use, and entries per agent. `//` comments explain each
168
+ section and each record type's purpose. Use it to keep type names consistent
169
+ within a repo. The export is local, like the memory file; ignore both with
170
+ `.agents/*memory.local.*`.
171
+
172
+ ## Develop
173
+
174
+ ```powershell
175
+ py -3.13 -m venv .venv
176
+ .venv/Scripts/python.exe -m pip install -e . --group dev
177
+ .venv/Scripts/python.exe -m pytest
178
+ ```
179
+
180
+ To have agents run your working copy instead of the published package,
181
+ install it as an editable tool and use `plain-text-memory-mcp` as the
182
+ config's command, with no args:
183
+
184
+ ```powershell
185
+ uv tool install -e .
186
+ ```
187
+
188
+ Code changes then take effect the next time an agent starts the server.
189
+
190
+ ## License
191
+
192
+ MIT, copyright Plain Text Office LLC. See [LICENSE](LICENSE).
@@ -0,0 +1,73 @@
1
+ #:schema https://json.schemastore.org/pyproject.json
2
+
3
+ [build-system]
4
+ requires = ["setuptools>=77"]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "plain-text-memory-mcp"
9
+ version = "0.1.0"
10
+ description = "Knowledge-graph memory MCP server that tags entries with date and agent"
11
+ readme = "README.md"
12
+ requires-python = ">=3.12"
13
+ license = "MIT"
14
+ license-files = ["LICENSE"]
15
+ authors = [{ name = "Plain Text Office LLC" }]
16
+ keywords = [
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "memory",
20
+ "knowledge-graph",
21
+ "claude-code",
22
+ "codex",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Environment :: Console",
27
+ "Intended Audience :: Developers",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Topic :: Software Development",
33
+ ]
34
+ dependencies = ["mcp>=2.2,<3"]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/PlainTxtOffice/plain-text-memory-mcp"
38
+ Issues = "https://github.com/PlainTxtOffice/plain-text-memory-mcp/issues"
39
+
40
+ [project.scripts]
41
+ plain-text-memory-mcp = "plain_text_memory_mcp.server:main"
42
+
43
+ [dependency-groups]
44
+ dev = ["pytest>=8"]
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.setuptools.package-data]
50
+ plain_text_memory_mcp = ["taxonomy.json", "prompts/*.md"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+ pythonpath = ["scripts"]
55
+
56
+
57
+ [tool.mypy]
58
+ exclude = ['(^|[\\/])tests([\\/]|$)']
59
+ ignore_missing_imports = true
60
+ [tool.ruff]
61
+ line-length = 80
62
+
63
+ [tool.rumdl]
64
+ line-length = 80
65
+ respect-gitignore = true
66
+ exclude = [".venv", "build", "dist"]
67
+
68
+ [tool.rumdl.MD029]
69
+ style = "one"
70
+
71
+ [tool.rumdl.MD013]
72
+ code-blocks = false
73
+ tables = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ """Knowledge-graph memory MCP server with date and agent tags."""
@@ -0,0 +1,24 @@
1
+ """Name the agent that is calling the memory server."""
2
+
3
+ UNKNOWN_AGENT = "unknown"
4
+
5
+ KNOWN_AGENTS = (("claude", "claude-code"), ("codex", "codex"))
6
+
7
+
8
+ def calc_agent_name(client_name: str | None, override: str | None) -> str:
9
+ """Return the agent name recorded in memory tags.
10
+
11
+ - A non-blank override, from `MEMORY_AGENT`, always wins.
12
+ - Client names containing "claude" or "codex" map to `claude-code` and
13
+ `codex`; any other name is recorded as sent.
14
+ - A missing or blank client name records `unknown`.
15
+ """
16
+ if override and override.strip():
17
+ return override.strip()
18
+ if not client_name or not client_name.strip():
19
+ return UNKNOWN_AGENT
20
+ lowered = client_name.lower()
21
+ for marker, agent_name in KNOWN_AGENTS:
22
+ if marker in lowered:
23
+ return agent_name
24
+ return client_name.strip()