agent-context-graph 0.3.0__tar.gz → 0.3.1__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 (40) hide show
  1. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/PKG-INFO +3 -3
  2. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/pyproject.toml +3 -3
  3. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/mcp_server.py +20 -7
  4. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_tools.py +35 -17
  5. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/.gitignore +0 -0
  6. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/CONTEXT.md +0 -0
  7. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/LICENSE +0 -0
  8. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/README.md +0 -0
  9. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/docs/adr/0001-runtime-adapter-terminology.md +0 -0
  10. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/docs/adr/0002-config-file-only-hook-resolution.md +0 -0
  11. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/docs/adr/0003-config-path-override.md +0 -0
  12. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/docs/adr/0004-user-id-in-session-start-event.md +0 -0
  13. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/docs/adr/0005-config-file-covers-llm-key-and-subprocess-env.md +0 -0
  14. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/docs/command-hooks.md +0 -0
  15. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/__init__.py +0 -0
  16. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/adapters/__init__.py +0 -0
  17. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/adapters/_identity.py +0 -0
  18. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/adapters/claude.py +0 -0
  19. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/adapters/claude_code.py +0 -0
  20. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/adapters/codex.py +0 -0
  21. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/adapters/openai.py +0 -0
  22. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/cli.py +0 -0
  23. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/events.py +0 -0
  24. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/hooks/__init__.py +0 -0
  25. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/hooks/cli.py +0 -0
  26. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/hooks/runner.py +0 -0
  27. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/hooks/runtime_plugin.py +0 -0
  28. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/link.py +0 -0
  29. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/protocols.py +0 -0
  30. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/src/agent_context_graph/tools.py +0 -0
  31. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/__init__.py +0 -0
  32. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_claude_code_adapter.py +0 -0
  33. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_codex_adapter.py +0 -0
  34. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_config_cli.py +0 -0
  35. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_events.py +0 -0
  36. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_hook_cli.py +0 -0
  37. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_identity.py +0 -0
  38. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_link.py +0 -0
  39. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_runner_sessions_graph_connector.py +0 -0
  40. {agent_context_graph-0.3.0 → agent_context_graph-0.3.1}/tests/test_runtime_plugin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-context-graph
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Connect agent SDKs to context-graph components (actions-graph, skills-graph, etc.)
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -13,11 +13,11 @@ Requires-Dist: memgraph-toolbox>=0.1.11
13
13
  Provides-Extra: claude
14
14
  Requires-Dist: claude-agent-sdk>=0.1.0; extra == 'claude'
15
15
  Provides-Extra: mcp
16
- Requires-Dist: mcp>=1.23.0; extra == 'mcp'
16
+ Requires-Dist: mcp<3,>=1.23.0; extra == 'mcp'
17
17
  Provides-Extra: openai
18
18
  Requires-Dist: openai-agents>=0.1.0; extra == 'openai'
19
19
  Provides-Extra: test
20
- Requires-Dist: mcp>=1.23.0; extra == 'test'
20
+ Requires-Dist: mcp<3,>=1.23.0; extra == 'test'
21
21
  Requires-Dist: pytest-asyncio>=0.24.0; extra == 'test'
22
22
  Requires-Dist: pytest>=9.0.3; extra == 'test'
23
23
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-context-graph"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "Connect agent SDKs to context-graph components (actions-graph, skills-graph, etc.)"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -27,13 +27,13 @@ claude = [
27
27
  "claude-agent-sdk>=0.1.0",
28
28
  ]
29
29
  mcp = [
30
- "mcp>=1.23.0",
30
+ "mcp>=1.23.0,<3",
31
31
  ]
32
32
  openai = [
33
33
  "openai-agents>=0.1.0",
34
34
  ]
35
35
  test = [
36
- "mcp>=1.23.0",
36
+ "mcp>=1.23.0,<3",
37
37
  "pytest>=9.0.3",
38
38
  "pytest-asyncio>=0.24.0",
39
39
  ]
@@ -23,7 +23,11 @@ SERVER_NAME = "context-graph"
23
23
 
24
24
 
25
25
  def build_server(tools: dict[str, Tool] | None = None) -> Server:
26
- """An MCP server exposing ``tools``, all registered tools by default."""
26
+ """An MCP server exposing ``tools``, all registered tools by default.
27
+
28
+ Supports mcp 1.x, which registers handlers through decorators, and 2.x,
29
+ which takes them as constructor arguments; the protocol is the same.
30
+ """
27
31
  import mcp.types as types
28
32
  from anyio import to_thread
29
33
  from mcp.server.lowlevel import Server
@@ -31,28 +35,37 @@ def build_server(tools: dict[str, Tool] | None = None) -> Server:
31
35
  from agent_context_graph.adapters._identity import load_config
32
36
 
33
37
  tools = load_tools() if tools is None else tools
34
- server: Server = Server(SERVER_NAME)
35
38
 
36
- @server.list_tools()
37
39
  async def list_tools() -> list[types.Tool]:
38
40
  return [
39
41
  types.Tool(name=tool.name, description=tool.description, inputSchema=tool.input_schema)
40
42
  for tool in tools.values()
41
43
  ]
42
44
 
43
- @server.call_tool()
44
- async def call_tool(name: str, arguments: dict[str, Any]) -> types.CallToolResult:
45
+ async def call_tool(name: str, arguments: dict[str, Any] | None) -> types.CallToolResult:
45
46
  tool = tools.get(name)
46
47
  if tool is None:
47
48
  return _error(f"Unknown tool: {name}")
48
49
  try:
49
50
  # Tools query Memgraph synchronously; a thread keeps the server responsive.
50
- result = await to_thread.run_sync(tool.call, arguments, load_config())
51
+ result = await to_thread.run_sync(tool.call, arguments or {}, load_config())
51
52
  except ToolError as exc:
52
53
  return _error(str(exc))
53
54
  return types.CallToolResult(content=[types.TextContent(type="text", text=result.text)])
54
55
 
55
- return server
56
+ if hasattr(Server, "call_tool"): # mcp 1.x
57
+ server: Server = Server(SERVER_NAME)
58
+ server.list_tools()(list_tools)
59
+ server.call_tool()(call_tool)
60
+ return server
61
+
62
+ async def on_list_tools(ctx: Any, params: Any) -> types.ListToolsResult:
63
+ return types.ListToolsResult(tools=await list_tools())
64
+
65
+ async def on_call_tool(ctx: Any, params: Any) -> types.CallToolResult:
66
+ return await call_tool(params.name, params.arguments)
67
+
68
+ return Server(SERVER_NAME, on_list_tools=on_list_tools, on_call_tool=on_call_tool)
56
69
 
57
70
 
58
71
  def serve() -> int:
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import io
11
11
  import json
12
+ from contextlib import asynccontextmanager
12
13
  from dataclasses import dataclass
13
14
  from typing import Any, ClassVar
14
15
 
@@ -58,38 +59,55 @@ def tools(monkeypatch):
58
59
  return registered
59
60
 
60
61
 
62
+ @asynccontextmanager
63
+ async def _connected(server):
64
+ """A client session talking to ``server`` in memory, the same way under mcp 1.x and 2.x."""
65
+ import anyio
66
+ from mcp import ClientSession
67
+ from mcp.shared.memory import create_client_server_memory_streams
68
+
69
+ async with (
70
+ create_client_server_memory_streams() as (client_streams, server_streams),
71
+ anyio.create_task_group() as tg,
72
+ ):
73
+ tg.start_soon(lambda: server.run(*server_streams, server.create_initialization_options()))
74
+ async with ClientSession(*client_streams) as client:
75
+ await client.initialize()
76
+ yield client
77
+ tg.cancel_scope.cancel()
78
+
79
+
80
+ def _wire(model) -> dict:
81
+ """A result as the harness receives it: mcp 2.x renamed the Python fields, not the JSON."""
82
+ return model.model_dump(by_alias=True, exclude_none=True)
83
+
84
+
61
85
  @pytest.mark.asyncio
62
86
  async def test_mcp_lists_tools_and_returns_only_the_text(tools):
63
87
  """Text only: Claude Code and Codex both show structuredContent instead of the text when both are sent."""
64
88
  pytest.importorskip("mcp")
65
- from mcp.shared.memory import create_connected_server_and_client_session
66
- from mcp.types import TextContent
67
-
68
89
  from agent_context_graph.mcp_server import build_server
69
90
 
70
- async with create_connected_server_and_client_session(build_server(tools)) as client:
71
- listed = await client.list_tools()
72
- result = await client.call_tool("recall", {"question": "deploy day"})
91
+ async with _connected(build_server(tools)) as client:
92
+ listed = _wire(await client.list_tools())
93
+ result = _wire(await client.call_tool("recall", {"question": "deploy day"}))
73
94
 
74
- assert [(tool.name, tool.inputSchema["required"]) for tool in listed.tools] == [("recall", ["question"])]
75
- assert not result.isError
76
- assert result.content == [TextContent(type="text", text="rows for deploy day of ante")]
77
- assert result.structuredContent is None
95
+ assert [(tool["name"], tool["inputSchema"]["required"]) for tool in listed["tools"]] == [("recall", ["question"])]
96
+ assert not result.get("isError")
97
+ assert result["content"] == [{"type": "text", "text": "rows for deploy day of ante"}]
98
+ assert "structuredContent" not in result
78
99
 
79
100
 
80
101
  @pytest.mark.asyncio
81
102
  async def test_mcp_reports_a_tool_error_to_the_model(tools):
82
103
  pytest.importorskip("mcp")
83
- from mcp.shared.memory import create_connected_server_and_client_session
84
- from mcp.types import TextContent
85
-
86
104
  from agent_context_graph.mcp_server import build_server
87
105
 
88
- async with create_connected_server_and_client_session(build_server(tools)) as client:
89
- result = await client.call_tool("recall", {"question": "fail"})
106
+ async with _connected(build_server(tools)) as client:
107
+ result = _wire(await client.call_tool("recall", {"question": "fail"}))
90
108
 
91
- assert result.isError
92
- assert result.content == [TextContent(type="text", text="no user configured")]
109
+ assert result["isError"]
110
+ assert result["content"] == [{"type": "text", "text": "no user configured"}]
93
111
 
94
112
 
95
113
  def test_recall_cli_prints_the_text_or_the_json(tools, capsys):