haijun-agent-sdk 0.1.0__py3-none-any.whl

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 (33) hide show
  1. haijun_agent_sdk/__init__.py +770 -0
  2. haijun_agent_sdk/_bundled/.gitignore +3 -0
  3. haijun_agent_sdk/_cli_version.py +3 -0
  4. haijun_agent_sdk/_errors.py +131 -0
  5. haijun_agent_sdk/_internal/__init__.py +1 -0
  6. haijun_agent_sdk/_internal/_mcp_compat.py +151 -0
  7. haijun_agent_sdk/_internal/_task_compat.py +187 -0
  8. haijun_agent_sdk/_internal/client.py +193 -0
  9. haijun_agent_sdk/_internal/message_parser.py +389 -0
  10. haijun_agent_sdk/_internal/query.py +983 -0
  11. haijun_agent_sdk/_internal/sdk_mcp_bridge.py +382 -0
  12. haijun_agent_sdk/_internal/session_import.py +162 -0
  13. haijun_agent_sdk/_internal/session_mutations.py +957 -0
  14. haijun_agent_sdk/_internal/session_resume.py +612 -0
  15. haijun_agent_sdk/_internal/session_store.py +193 -0
  16. haijun_agent_sdk/_internal/session_store_validation.py +45 -0
  17. haijun_agent_sdk/_internal/session_summary.py +229 -0
  18. haijun_agent_sdk/_internal/sessions.py +2007 -0
  19. haijun_agent_sdk/_internal/transcript_mirror_batcher.py +218 -0
  20. haijun_agent_sdk/_internal/transport/__init__.py +87 -0
  21. haijun_agent_sdk/_internal/transport/subprocess_cli.py +1183 -0
  22. haijun_agent_sdk/_version.py +3 -0
  23. haijun_agent_sdk/client.py +597 -0
  24. haijun_agent_sdk/py.typed +0 -0
  25. haijun_agent_sdk/query.py +127 -0
  26. haijun_agent_sdk/telemetry.py +181 -0
  27. haijun_agent_sdk/testing/__init__.py +9 -0
  28. haijun_agent_sdk/testing/session_store_conformance.py +327 -0
  29. haijun_agent_sdk/types.py +2456 -0
  30. haijun_agent_sdk-0.1.0.dist-info/METADATA +405 -0
  31. haijun_agent_sdk-0.1.0.dist-info/RECORD +33 -0
  32. haijun_agent_sdk-0.1.0.dist-info/WHEEL +4 -0
  33. haijun_agent_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,770 @@
1
+ """Haijun SDK for Python."""
2
+
3
+ import logging
4
+ import sys
5
+ import types as builtin_types
6
+ from collections.abc import Awaitable, Callable
7
+ from dataclasses import dataclass
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Annotated,
11
+ Any,
12
+ Generic,
13
+ TypeVar,
14
+ Union,
15
+ get_args,
16
+ get_origin,
17
+ )
18
+
19
+ if sys.version_info >= (3, 11):
20
+ from typing import get_type_hints as _get_type_hints
21
+ from typing import is_typeddict
22
+ else:
23
+ # On 3.10, stdlib is_typeddict doesn't recognize typing_extensions.TypedDict
24
+ # subclasses, and stdlib get_type_hints doesn't strip NotRequired markers.
25
+ from typing_extensions import get_type_hints as _get_type_hints
26
+ from typing_extensions import is_typeddict
27
+
28
+ import jsonschema
29
+ from mcp.types import CallToolResult, Tool
30
+ from mcp.types import ToolAnnotations as _McpToolAnnotations
31
+
32
+ from ._errors import (
33
+ CLIConnectionError,
34
+ CLIJSONDecodeError,
35
+ CLINotFoundError,
36
+ HaijunSDKError,
37
+ ProcessError,
38
+ ResultError,
39
+ )
40
+ from ._internal._mcp_compat import build_tool_server
41
+ from ._internal.session_import import import_session_to_store
42
+ from ._internal.session_mutations import (
43
+ ForkSessionResult,
44
+ delete_session,
45
+ delete_session_via_store,
46
+ fork_session,
47
+ fork_session_via_store,
48
+ rename_session,
49
+ rename_session_via_store,
50
+ tag_session,
51
+ tag_session_via_store,
52
+ )
53
+ from ._internal.session_store import InMemorySessionStore, project_key_for_directory
54
+ from ._internal.session_summary import fold_session_summary
55
+ from ._internal.sessions import (
56
+ get_session_info,
57
+ get_session_info_from_store,
58
+ get_session_messages,
59
+ get_session_messages_from_store,
60
+ get_subagent_messages,
61
+ get_subagent_messages_from_store,
62
+ list_sessions,
63
+ list_sessions_from_store,
64
+ list_subagents,
65
+ list_subagents_from_store,
66
+ )
67
+ from ._internal.transport import Transport
68
+ from ._version import __version__
69
+ from .client import HaijunSDKClient
70
+ from .query import query
71
+ from .types import (
72
+ TERMINAL_TASK_STATUSES,
73
+ AgentDefinition,
74
+ AssistantMessage,
75
+ BaseHookInput,
76
+ CanUseTool,
77
+ CanUseToolShadowedWarning,
78
+ ContentBlock,
79
+ ContextUsageCategory,
80
+ ContextUsageResponse,
81
+ ConversationResetMessage,
82
+ DeferredToolUse,
83
+ EffortLevel,
84
+ HaijunAgentOptions,
85
+ HookCallback,
86
+ HookContext,
87
+ HookEventMessage,
88
+ HookInput,
89
+ HookJSONOutput,
90
+ HookMatcher,
91
+ McpSdkServerConfig,
92
+ McpServerConfig,
93
+ McpServerConnectionStatus,
94
+ McpServerInfo,
95
+ McpServerStatus,
96
+ McpServerStatusConfig,
97
+ McpStatusResponse,
98
+ McpToolAnnotations,
99
+ McpToolInfo,
100
+ Message,
101
+ MessageOrigin,
102
+ MessageOriginKind,
103
+ MirrorErrorMessage,
104
+ ModelUsage,
105
+ NotificationHookInput,
106
+ NotificationHookSpecificOutput,
107
+ PermissionMode,
108
+ PermissionRequestHookInput,
109
+ PermissionRequestHookSpecificOutput,
110
+ PermissionResult,
111
+ PermissionResultAllow,
112
+ PermissionResultDeny,
113
+ PermissionUpdate,
114
+ PostToolUseFailureHookInput,
115
+ PostToolUseFailureHookSpecificOutput,
116
+ PostToolUseHookInput,
117
+ PreCompactHookInput,
118
+ PreToolUseHookInput,
119
+ RateLimitEvent,
120
+ RateLimitInfo,
121
+ RateLimitStatus,
122
+ RateLimitType,
123
+ ResultMessage,
124
+ SandboxIgnoreViolations,
125
+ SandboxNetworkConfig,
126
+ SandboxSettings,
127
+ SdkBeta,
128
+ SdkPluginConfig,
129
+ SDKSessionInfo,
130
+ ServerToolName,
131
+ ServerToolResultBlock,
132
+ ServerToolUseBlock,
133
+ SessionKey,
134
+ SessionListSubkeysKey,
135
+ SessionMessage,
136
+ SessionStore,
137
+ SessionStoreEntry,
138
+ SessionStoreFlushMode,
139
+ SessionStoreListEntry,
140
+ SessionSummaryEntry,
141
+ SettingSource,
142
+ StopHookInput,
143
+ StreamEvent,
144
+ SubagentStartHookInput,
145
+ SubagentStartHookSpecificOutput,
146
+ SubagentStopHookInput,
147
+ SystemMessage,
148
+ TaskBudget,
149
+ TaskNotificationMessage,
150
+ TaskNotificationOriginSubkind,
151
+ TaskNotificationStatus,
152
+ TaskProgressMessage,
153
+ TaskStartedMessage,
154
+ TaskUpdatedMessage,
155
+ TaskUpdatedStatus,
156
+ TaskUsage,
157
+ TextBlock,
158
+ ThinkingBlock,
159
+ ThinkingConfig,
160
+ ThinkingConfigAdaptive,
161
+ ThinkingConfigDisabled,
162
+ ThinkingConfigEnabled,
163
+ ToolPermissionContext,
164
+ ToolResultBlock,
165
+ ToolUseBlock,
166
+ UserMessage,
167
+ UserPromptSubmitHookInput,
168
+ )
169
+
170
+ # MCP Server Support
171
+
172
+ logger = logging.getLogger(__name__)
173
+
174
+ T = TypeVar("T")
175
+
176
+
177
+ # The snake_case spelling of each hint, mapped to its wire (camelCase) name.
178
+ _HINT_WIRE_NAMES = {
179
+ "read_only_hint": "readOnlyHint",
180
+ "destructive_hint": "destructiveHint",
181
+ "idempotent_hint": "idempotentHint",
182
+ "open_world_hint": "openWorldHint",
183
+ "max_result_size_chars": "maxResultSizeChars",
184
+ }
185
+
186
+
187
+ class ToolAnnotations(_McpToolAnnotations, extra="allow"):
188
+ """Hints about a tool's behavior, plus ``maxResultSizeChars``.
189
+
190
+ A ``mcp.types.ToolAnnotations`` that takes every hint in either spelling
191
+ on every supported mcp version: ``ToolAnnotations(readOnlyHint=True)``
192
+ and ``ToolAnnotations(read_only_hint=True)`` are the same thing, and both
193
+ type-check. (mcp's own class differs between majors here: 1.x only knows
194
+ the camelCase names and 2.x prefers snake_case.) Attribute access follows
195
+ the installed mcp: ``.readOnlyHint`` on 1.x, ``.read_only_hint`` on 2.x.
196
+
197
+ ``maxResultSizeChars`` (``max_result_size_chars``) is not an MCP hint but
198
+ a Haijun Code one: the size, in characters, up to which Haijun Code keeps
199
+ this tool's result inline instead of persisting it to a file and showing
200
+ a preview. It travels to the CLI in the tool's ``_meta``.
201
+
202
+ Either this class or a plain ``mcp.types.ToolAnnotations`` is accepted
203
+ wherever the SDK takes annotations.
204
+ """
205
+
206
+ maxResultSizeChars: int | None = None # noqa: N815 - the wire spelling
207
+
208
+ if TYPE_CHECKING:
209
+
210
+ def __init__( # both spellings, deliberately
211
+ self,
212
+ *,
213
+ title: str | None = None,
214
+ readOnlyHint: bool | None = None, # noqa: N803
215
+ read_only_hint: bool | None = None,
216
+ destructiveHint: bool | None = None, # noqa: N803
217
+ destructive_hint: bool | None = None,
218
+ idempotentHint: bool | None = None, # noqa: N803
219
+ idempotent_hint: bool | None = None,
220
+ openWorldHint: bool | None = None, # noqa: N803
221
+ open_world_hint: bool | None = None,
222
+ maxResultSizeChars: int | None = None, # noqa: N803
223
+ max_result_size_chars: int | None = None,
224
+ **extra: Any,
225
+ ) -> None: ...
226
+
227
+ else:
228
+
229
+ def __init__(self, **data: Any) -> None:
230
+ # Fold the snake_case spellings into the wire names, which every
231
+ # mcp version accepts (as field names on 1.x, as aliases on 2.x).
232
+ # Given both, the wire name wins.
233
+ for snake, wire in _HINT_WIRE_NAMES.items():
234
+ if snake in data:
235
+ value = data.pop(snake)
236
+ data.setdefault(wire, value)
237
+ super().__init__(**data)
238
+
239
+
240
+ @dataclass
241
+ class SdkMcpTool(Generic[T]):
242
+ """Definition for an SDK MCP tool."""
243
+
244
+ name: str
245
+ description: str
246
+ input_schema: type[T] | dict[str, Any]
247
+ handler: Callable[[T], Awaitable[dict[str, Any]]]
248
+ annotations: _McpToolAnnotations | None = None
249
+
250
+
251
+ def tool(
252
+ name: str,
253
+ description: str,
254
+ input_schema: type | dict[str, Any],
255
+ annotations: _McpToolAnnotations | None = None,
256
+ ) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]:
257
+ """Decorator for defining MCP tools with type safety.
258
+
259
+ Creates a tool that can be used with SDK MCP servers. The tool runs
260
+ in-process within your Python application, providing better performance
261
+ than external MCP servers.
262
+
263
+ Args:
264
+ name: Unique identifier for the tool. This is what Haijun will use
265
+ to reference the tool in function calls.
266
+ description: Human-readable description of what the tool does.
267
+ This helps Haijun understand when to use the tool.
268
+ input_schema: Schema defining the tool's input parameters.
269
+ Can be either:
270
+ - A dictionary mapping parameter names to types (e.g., {"text": str})
271
+ - A TypedDict class for more complex schemas
272
+ - A JSON Schema dictionary for full validation
273
+ Use ``Annotated[type, "description"]`` to add a description to a
274
+ parameter in either dict-style or TypedDict schemas.
275
+ annotations: Optional MCP tool annotations (hints such as
276
+ ``readOnlyHint`` or ``destructiveHint``) advertised to Haijun.
277
+ ``ToolAnnotations(maxResultSizeChars=N)`` additionally raises the
278
+ size up to which Haijun Code keeps this tool's result inline
279
+ instead of persisting it to a file and showing a preview.
280
+
281
+ Returns:
282
+ A decorator function that wraps the tool implementation and returns
283
+ an SdkMcpTool instance ready for use with create_sdk_mcp_server().
284
+
285
+ Example:
286
+ Basic tool with simple schema:
287
+ >>> @tool("greet", "Greet a user", {"name": str})
288
+ ... async def greet(args):
289
+ ... return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
290
+
291
+ Tool with multiple parameters:
292
+ >>> @tool("add", "Add two numbers", {"a": float, "b": float})
293
+ ... async def add_numbers(args):
294
+ ... result = args["a"] + args["b"]
295
+ ... return {"content": [{"type": "text", "text": f"Result: {result}"}]}
296
+
297
+ Tool with error handling:
298
+ >>> @tool("divide", "Divide two numbers", {"a": float, "b": float})
299
+ ... async def divide(args):
300
+ ... if args["b"] == 0:
301
+ ... return {"content": [{"type": "text", "text": "Error: Division by zero"}], "is_error": True}
302
+ ... return {"content": [{"type": "text", "text": f"Result: {args['a'] / args['b']}"}]}
303
+
304
+ Read-only tool that may return a large result:
305
+ >>> @tool(
306
+ ... "get_schema",
307
+ ... "Return the database schema",
308
+ ... {},
309
+ ... annotations=ToolAnnotations(readOnlyHint=True, maxResultSizeChars=500_000),
310
+ ... )
311
+ ... async def get_schema(args):
312
+ ... return {"content": [{"type": "text", "text": load_schema()}]}
313
+
314
+ Notes:
315
+ - The tool function must be async (defined with async def)
316
+ - The function receives a single dict argument with the input parameters
317
+ - Arguments are validated against the input schema before the function
318
+ is called; invalid input is reported back to Haijun as an error result
319
+ - The function should return a dict with a "content" key containing the response
320
+ - Errors can be indicated by including "is_error": True in the response;
321
+ an exception raised by the function is reported the same way
322
+ """
323
+
324
+ def decorator(
325
+ handler: Callable[[Any], Awaitable[dict[str, Any]]],
326
+ ) -> SdkMcpTool[Any]:
327
+ return SdkMcpTool(
328
+ name=name,
329
+ description=description,
330
+ input_schema=input_schema,
331
+ handler=handler,
332
+ annotations=annotations,
333
+ )
334
+
335
+ return decorator
336
+
337
+
338
+ def _python_type_to_json_schema(py_type: Any) -> dict[str, Any]:
339
+ """Convert a Python type annotation to a JSON Schema dict."""
340
+ origin = get_origin(py_type)
341
+
342
+ # NotRequired/Required/ReadOnly survive include_extras=True; unwrap them
343
+ if getattr(origin, "_name", None) in ("NotRequired", "Required", "ReadOnly"):
344
+ return _python_type_to_json_schema(get_args(py_type)[0])
345
+
346
+ if origin is Annotated:
347
+ args = get_args(py_type)
348
+ schema = _python_type_to_json_schema(args[0])
349
+ for meta in args[1:]:
350
+ if isinstance(meta, str):
351
+ schema["description"] = meta
352
+ break
353
+ return schema
354
+
355
+ if py_type is str:
356
+ return {"type": "string"}
357
+ if py_type is int:
358
+ return {"type": "integer"}
359
+ if py_type is float:
360
+ return {"type": "number"}
361
+ if py_type is bool:
362
+ return {"type": "boolean"}
363
+
364
+ origin = getattr(py_type, "__origin__", None)
365
+
366
+ if origin is Union or isinstance(py_type, builtin_types.UnionType):
367
+ args = py_type.__args__
368
+ non_none = [a for a in args if a is not builtin_types.NoneType]
369
+ if len(non_none) == 1:
370
+ return _python_type_to_json_schema(non_none[0])
371
+ return {"anyOf": [_python_type_to_json_schema(a) for a in non_none]}
372
+
373
+ if origin is list:
374
+ item_args = getattr(py_type, "__args__", None)
375
+ if item_args:
376
+ return {"type": "array", "items": _python_type_to_json_schema(item_args[0])}
377
+ return {"type": "array"}
378
+ if origin is dict:
379
+ return {"type": "object"}
380
+
381
+ if py_type is list:
382
+ return {"type": "array"}
383
+ if py_type is dict:
384
+ return {"type": "object"}
385
+
386
+ if is_typeddict(py_type):
387
+ return _typeddict_to_json_schema(py_type)
388
+
389
+ return {"type": "string"}
390
+
391
+
392
+ def _typeddict_to_json_schema(td_class: type) -> dict[str, Any]:
393
+ """Convert a TypedDict class to a JSON Schema dict."""
394
+ hints = _get_type_hints(td_class, include_extras=True)
395
+
396
+ properties: dict[str, Any] = {}
397
+ for field_name, field_type in hints.items():
398
+ properties[field_name] = _python_type_to_json_schema(field_type)
399
+
400
+ required_keys = getattr(td_class, "__required_keys__", set(properties.keys()))
401
+ schema: dict[str, Any] = {
402
+ "type": "object",
403
+ "properties": properties,
404
+ }
405
+ if required_keys:
406
+ schema["required"] = sorted(required_keys)
407
+ return schema
408
+
409
+
410
+ def _build_input_schema(tool_def: SdkMcpTool[Any]) -> dict[str, Any]:
411
+ """Turn a tool's declared input_schema into the JSON Schema sent on the wire."""
412
+ if isinstance(tool_def.input_schema, dict):
413
+ if (
414
+ "type" in tool_def.input_schema
415
+ and "properties" in tool_def.input_schema
416
+ and isinstance(tool_def.input_schema["type"], str)
417
+ ):
418
+ return tool_def.input_schema
419
+ properties = {
420
+ param_name: _python_type_to_json_schema(param_type)
421
+ for param_name, param_type in tool_def.input_schema.items()
422
+ }
423
+ return {
424
+ "type": "object",
425
+ "properties": properties,
426
+ "required": list(properties.keys()),
427
+ }
428
+ if is_typeddict(tool_def.input_schema):
429
+ return _typeddict_to_json_schema(tool_def.input_schema)
430
+ return {"type": "object", "properties": {}}
431
+
432
+
433
+ def _build_meta(tool_def: SdkMcpTool[Any]) -> dict[str, Any] | None:
434
+ # Client-specific hints travel in _meta under namespaced keys because MCP
435
+ # clients drop annotation fields they do not know. maxResultSizeChars is
436
+ # the size up to which Haijun Code keeps a tool result inline rather than
437
+ # persisting it and showing a preview; it rides on the annotations object
438
+ # as an extra (or subclass-declared) field.
439
+ max_size = getattr(tool_def.annotations, "maxResultSizeChars", None)
440
+ if max_size is None:
441
+ return None
442
+ return {"haijun/maxResultSizeChars": max_size}
443
+
444
+
445
+ def _tool_error_result(message: str) -> CallToolResult:
446
+ return CallToolResult.model_validate(
447
+ {"content": [{"type": "text", "text": message}], "isError": True}
448
+ )
449
+
450
+
451
+ def _convert_tool_content(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
452
+ """Map the content of a tool handler's result dict to MCP content blocks.
453
+
454
+ Text and image blocks pass through. Resource links and text resources are
455
+ flattened to text because that is what the CLI renders; binary resources
456
+ and unknown block types are dropped with a warning.
457
+ """
458
+ content: list[dict[str, Any]] = []
459
+ for item in items:
460
+ item_type = item.get("type")
461
+ if item_type == "text":
462
+ content.append({"type": "text", "text": item["text"]})
463
+ elif item_type == "image":
464
+ content.append(
465
+ {"type": "image", "data": item["data"], "mimeType": item["mimeType"]}
466
+ )
467
+ elif item_type == "resource_link":
468
+ parts = [
469
+ str(part)
470
+ for part in (item.get("name"), item.get("uri"), item.get("description"))
471
+ if part
472
+ ]
473
+ content.append(
474
+ {"type": "text", "text": "\n".join(parts) if parts else "Resource link"}
475
+ )
476
+ elif item_type == "resource":
477
+ resource = item.get("resource") or {}
478
+ if "text" in resource:
479
+ content.append({"type": "text", "text": resource["text"]})
480
+ else:
481
+ logger.warning(
482
+ "Binary embedded resource cannot be converted to text, skipping"
483
+ )
484
+ else:
485
+ logger.warning(
486
+ "Unsupported content type %r in tool result, skipping", item_type )
487
+ return content
488
+
489
+
490
+ def create_sdk_mcp_server(
491
+ name: str, version: str = "1.0.0", tools: list[SdkMcpTool[Any]] | None = None ) -> McpSdkServerConfig:
492
+ """Create an in-process MCP server that runs within your Python application.
493
+
494
+ Unlike external MCP servers that run as separate processes, SDK MCP servers
495
+ run directly in your application's process. This provides:
496
+ - Better performance (no IPC overhead)
497
+ - Simpler deployment (single process)
498
+ - Easier debugging (same process)
499
+ - Direct access to your application's state
500
+
501
+ Args:
502
+ name: Unique identifier for the server. This name is used to reference
503
+ the server in the mcp_servers configuration.
504
+ version: Server version string. Defaults to "1.0.0". This is for
505
+ informational purposes and doesn't affect functionality.
506
+ tools: List of SdkMcpTool instances created with the @tool decorator.
507
+ These are the functions that Haijun can call through this server.
508
+ If None or empty, the server will have no tools (rarely useful).
509
+
510
+ Returns:
511
+ McpSdkServerConfig: A configuration object that can be passed to
512
+ HaijunAgentOptions.mcp_servers. Its ``instance`` is a regular
513
+ ``mcp.server.Server`` that the SDK connects to Haijun Code over an
514
+ in-memory MCP transport.
515
+
516
+ Example:
517
+ Simple calculator server:
518
+ >>> @tool("add", "Add numbers", {"a": float, "b": float})
519
+ ... async def add(args):
520
+ ... return {"content": [{"type": "text", "text": f"Sum: {args['a'] + args['b']}"}]}
521
+ >>>
522
+ >>> @tool("multiply", "Multiply numbers", {"a": float, "b": float})
523
+ ... async def multiply(args):
524
+ ... return {"content": [{"type": "text", "text": f"Product: {args['a'] * args['b']}"}]}
525
+ >>>
526
+ >>> calculator = create_sdk_mcp_server(
527
+ ... name="calculator",
528
+ ... version="2.0.0",
529
+ ... tools=[add, multiply]
530
+ ... )
531
+ >>>
532
+ >>> # Use with Haijun
533
+ >>> options = HaijunAgentOptions(
534
+ ... mcp_servers={"calc": calculator},
535
+ ... allowed_tools=["add", "multiply"]
536
+ ... )
537
+
538
+ Server with application state access:
539
+ >>> class DataStore:
540
+ ... def __init__(self):
541
+ ... self.items = []
542
+ ...
543
+ >>> store = DataStore()
544
+ >>>
545
+ >>> @tool("add_item", "Add item to store", {"item": str})
546
+ ... async def add_item(args):
547
+ ... store.items.append(args["item"])
548
+ ... return {"content": [{"type": "text", "text": f"Added: {args['item']}"}]}
549
+ >>>
550
+ >>> server = create_sdk_mcp_server("store", tools=[add_item])
551
+
552
+ Bringing your own server: any ``mcp.server.Server`` built with the
553
+ installed ``mcp`` package can be used in place of this helper, and
554
+ the requests it handles (tools, resources, prompts, ...) are served.
555
+ Requests and notifications the server sends to the client (sampling,
556
+ elicitation, roots, logging, progress) are not forwarded yet:
557
+ >>> from mcp.server import Server
558
+ >>> my_server = Server("mine") # register handlers with the mcp API
559
+ >>> config = McpSdkServerConfig(type="sdk", name="mine", instance=my_server)
560
+
561
+ Notes:
562
+ - The server runs in the same process as your Python application
563
+ - Tools have direct access to your application's variables and state
564
+ - No subprocess or IPC overhead for tool calls
565
+ - Server lifecycle is managed automatically by the SDK
566
+ - Tool arguments are validated against the tool's input schema, and
567
+ unknown tools, invalid arguments and exceptions raised by a handler
568
+ are all reported to Haijun as error results rather than failing the
569
+ request
570
+ - Works with both mcp 1.x and mcp 2.x
571
+
572
+ See Also:
573
+ - tool(): Decorator for creating tool functions
574
+ - HaijunAgentOptions: Configuration for using servers with query()
575
+ """
576
+ tools = tools or []
577
+ tools_by_name = {tool_def.name: tool_def for tool_def in tools}
578
+ schemas = {tool_def.name: _build_input_schema(tool_def) for tool_def in tools}
579
+ wire_tools = [
580
+ Tool.model_validate(
581
+ {
582
+ "name": tool_def.name,
583
+ "description": tool_def.description,
584
+ "inputSchema": schemas[tool_def.name],
585
+ "annotations": tool_def.annotations,
586
+ "_meta": _build_meta(tool_def),
587
+ }
588
+ )
589
+ for tool_def in tools
590
+ ]
591
+
592
+ async def run_tool(tool_name: str, arguments: dict[str, Any]) -> CallToolResult:
593
+ # The SDK owns these semantics so a tool behaves the same on every
594
+ # supported mcp version: unknown tools, invalid arguments and handler
595
+ # failures all come back as isError results the model can read, never
596
+ # as protocol errors.
597
+ tool_def = tools_by_name.get(tool_name)
598
+ if tool_def is None:
599
+ return _tool_error_result(f"Tool '{tool_name}' not found")
600
+ try:
601
+ try:
602
+ jsonschema.validate(instance=arguments, schema=schemas[tool_name])
603
+ except jsonschema.ValidationError as e:
604
+ return _tool_error_result(f"Input validation error: {e.message}")
605
+ result = await tool_def.handler(arguments)
606
+ return CallToolResult.model_validate(
607
+ {
608
+ "content": _convert_tool_content(result.get("content", [])),
609
+ "isError": result.get("is_error", False),
610
+ }
611
+ )
612
+ except Exception as e:
613
+ return _tool_error_result(str(e))
614
+
615
+ server = build_tool_server(name, version, wire_tools, run_tool)
616
+
617
+ return McpSdkServerConfig(type="sdk", name=name, instance=server)
618
+
619
+
620
+ __all__ = [
621
+ # Main exports
622
+ "query",
623
+ "__version__",
624
+ # Transport
625
+ "Transport",
626
+ "HaijunSDKClient",
627
+ # Types
628
+ "PermissionMode",
629
+ "EffortLevel",
630
+ "McpServerConfig",
631
+ "McpSdkServerConfig",
632
+ "McpServerStatus",
633
+ "McpServerStatusConfig",
634
+ "McpServerConnectionStatus",
635
+ "McpServerInfo",
636
+ "McpStatusResponse",
637
+ "McpToolAnnotations",
638
+ "McpToolInfo",
639
+ "UserMessage",
640
+ "AssistantMessage",
641
+ "SystemMessage",
642
+ "TaskStartedMessage",
643
+ "TaskProgressMessage",
644
+ "TaskUpdatedMessage",
645
+ "TaskNotificationMessage",
646
+ "TaskNotificationStatus",
647
+ "TaskUpdatedStatus",
648
+ "TERMINAL_TASK_STATUSES",
649
+ "TaskUsage",
650
+ "ResultMessage",
651
+ "ModelUsage",
652
+ "DeferredToolUse",
653
+ "RateLimitEvent",
654
+ "RateLimitInfo",
655
+ "RateLimitStatus",
656
+ "RateLimitType",
657
+ "StreamEvent",
658
+ "ConversationResetMessage",
659
+ "Message",
660
+ "MessageOrigin",
661
+ "MessageOriginKind",
662
+ "TaskNotificationOriginSubkind",
663
+ "HaijunAgentOptions",
664
+ "TaskBudget",
665
+ "TextBlock",
666
+ "ThinkingBlock",
667
+ "ThinkingConfig",
668
+ "ThinkingConfigAdaptive",
669
+ "ThinkingConfigEnabled",
670
+ "ThinkingConfigDisabled",
671
+ "ToolUseBlock",
672
+ "ToolResultBlock",
673
+ "ServerToolName",
674
+ "ServerToolUseBlock",
675
+ "ServerToolResultBlock",
676
+ "ContentBlock",
677
+ "ContextUsageCategory",
678
+ "ContextUsageResponse",
679
+ # Tool callbacks
680
+ "CanUseTool",
681
+ "CanUseToolShadowedWarning",
682
+ "ToolPermissionContext",
683
+ "PermissionResult",
684
+ "PermissionResultAllow",
685
+ "PermissionResultDeny",
686
+ "PermissionUpdate",
687
+ # Hook support
688
+ "HookCallback",
689
+ "HookContext",
690
+ "HookInput",
691
+ "HookEventMessage",
692
+ "BaseHookInput",
693
+ "PreToolUseHookInput",
694
+ "PostToolUseHookInput",
695
+ "PostToolUseFailureHookInput",
696
+ "PostToolUseFailureHookSpecificOutput",
697
+ "UserPromptSubmitHookInput",
698
+ "StopHookInput",
699
+ "SubagentStopHookInput",
700
+ "PreCompactHookInput",
701
+ "NotificationHookInput",
702
+ "SubagentStartHookInput",
703
+ "PermissionRequestHookInput",
704
+ "NotificationHookSpecificOutput",
705
+ "SubagentStartHookSpecificOutput",
706
+ "PermissionRequestHookSpecificOutput",
707
+ "HookJSONOutput",
708
+ "HookMatcher",
709
+ # Agent support
710
+ "AgentDefinition",
711
+ "SettingSource",
712
+ # Plugin support
713
+ "SdkPluginConfig",
714
+ # Session listing
715
+ "list_sessions",
716
+ "get_session_info",
717
+ "get_session_messages",
718
+ "list_subagents",
719
+ "get_subagent_messages",
720
+ "SDKSessionInfo",
721
+ "SessionMessage",
722
+ # Session store
723
+ "SessionKey",
724
+ "SessionStore",
725
+ "SessionStoreEntry",
726
+ "SessionStoreFlushMode",
727
+ "SessionStoreListEntry",
728
+ "SessionSummaryEntry",
729
+ "SessionListSubkeysKey",
730
+ "InMemorySessionStore",
731
+ "fold_session_summary",
732
+ "MirrorErrorMessage",
733
+ "project_key_for_directory",
734
+ "import_session_to_store",
735
+ # Session listing (SessionStore-backed async variants)
736
+ "list_sessions_from_store",
737
+ "get_session_info_from_store",
738
+ "get_session_messages_from_store",
739
+ "list_subagents_from_store",
740
+ "get_subagent_messages_from_store",
741
+ # Session mutations
742
+ "rename_session",
743
+ "tag_session",
744
+ "delete_session",
745
+ "fork_session",
746
+ "ForkSessionResult",
747
+ # Session mutations (SessionStore-backed async variants)
748
+ "rename_session_via_store",
749
+ "tag_session_via_store",
750
+ "delete_session_via_store",
751
+ "fork_session_via_store",
752
+ # Beta support
753
+ "SdkBeta",
754
+ # Sandbox support
755
+ "SandboxSettings",
756
+ "SandboxNetworkConfig",
757
+ "SandboxIgnoreViolations",
758
+ # MCP Server Support
759
+ "create_sdk_mcp_server",
760
+ "tool",
761
+ "SdkMcpTool",
762
+ "ToolAnnotations",
763
+ # Errors
764
+ "HaijunSDKError",
765
+ "CLIConnectionError",
766
+ "CLINotFoundError",
767
+ "ProcessError",
768
+ "ResultError",
769
+ "CLIJSONDecodeError",
770
+ ]