docsif 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 (86) hide show
  1. docsif-0.1.0.dist-info/METADATA +393 -0
  2. docsif-0.1.0.dist-info/RECORD +86 -0
  3. docsif-0.1.0.dist-info/WHEEL +4 -0
  4. docsif-0.1.0.dist-info/entry_points.txt +3 -0
  5. docsif-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. sif/__init__.py +24 -0
  7. sif/_version.py +3 -0
  8. sif/cli/__init__.py +11 -0
  9. sif/cli/commands/__init__.py +18 -0
  10. sif/cli/commands/bench.py +148 -0
  11. sif/cli/commands/collection.py +360 -0
  12. sif/cli/commands/context.py +154 -0
  13. sif/cli/commands/get.py +200 -0
  14. sif/cli/commands/index.py +294 -0
  15. sif/cli/commands/ls.py +67 -0
  16. sif/cli/commands/mcp.py +93 -0
  17. sif/cli/commands/pull.py +111 -0
  18. sif/cli/commands/search.py +574 -0
  19. sif/cli/config.py +206 -0
  20. sif/cli/formatters.py +269 -0
  21. sif/cli/main.py +205 -0
  22. sif/config/__init__.py +30 -0
  23. sif/config/constants.py +52 -0
  24. sif/config/settings.py +217 -0
  25. sif/core/__init__.py +21 -0
  26. sif/core/collection.py +161 -0
  27. sif/core/context.py +167 -0
  28. sif/core/document.py +125 -0
  29. sif/core/models.py +298 -0
  30. sif/database/__init__.py +23 -0
  31. sif/database/connection.py +139 -0
  32. sif/database/database.py +136 -0
  33. sif/database/migrations.py +187 -0
  34. sif/database/repositories.py +500 -0
  35. sif/database/repository.py +171 -0
  36. sif/database/schema.py +362 -0
  37. sif/embedding/__init__.py +22 -0
  38. sif/embedding/cache.py +196 -0
  39. sif/embedding/embedder.py +328 -0
  40. sif/embedding/factory.py +92 -0
  41. sif/embedding/manager.py +200 -0
  42. sif/embedding/model.py +126 -0
  43. sif/indexing/__init__.py +21 -0
  44. sif/indexing/chunker.py +226 -0
  45. sif/indexing/indexer.py +226 -0
  46. sif/indexing/parser.py +192 -0
  47. sif/indexing/scanner.py +148 -0
  48. sif/indexing/watcher.py +156 -0
  49. sif/mcp/README.md +258 -0
  50. sif/mcp/__init__.py +9 -0
  51. sif/mcp/claude_desktop_config.json +16 -0
  52. sif/mcp/cli.py +92 -0
  53. sif/mcp/protocol.py +336 -0
  54. sif/mcp/server.py +433 -0
  55. sif/mcp/server_http.py +54 -0
  56. sif/mcp/test_mcp.py +108 -0
  57. sif/mcp/tools.py +497 -0
  58. sif/mcp/transport_http.py +424 -0
  59. sif/mcp/transport_stdio.py +292 -0
  60. sif/mcp_server/__init__.py +26 -0
  61. sif/mcp_server/handlers.py +68 -0
  62. sif/mcp_server/server.py +58 -0
  63. sif/mcp_server/tools.py +216 -0
  64. sif/mcp_server/transport.py +221 -0
  65. sif/models/__init__.py +70 -0
  66. sif/models/collection.py +51 -0
  67. sif/models/context.py +45 -0
  68. sif/models/document.py +76 -0
  69. sif/models/download.py +208 -0
  70. sif/models/embedding.py +71 -0
  71. sif/models/search.py +92 -0
  72. sif/search/__init__.py +22 -0
  73. sif/search/benchmark.py +110 -0
  74. sif/search/bm25.py +216 -0
  75. sif/search/expansion.py +157 -0
  76. sif/search/hybrid.py +323 -0
  77. sif/search/rerank.py +390 -0
  78. sif/search/rrf.py +186 -0
  79. sif/search/snippets.py +130 -0
  80. sif/search/strategy.py +97 -0
  81. sif/search/vector.py +177 -0
  82. sif/utils/__init__.py +26 -0
  83. sif/utils/logging.py +143 -0
  84. sif/utils/paths.py +82 -0
  85. sif/utils/progress.py +83 -0
  86. sif/utils/text.py +128 -0
sif/mcp/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # SIF MCP Server
2
+
3
+ Model Context Protocol (MCP) server implementation for SIF.
4
+
5
+ ## Features
6
+
7
+ - **Dual Transport Support**: stdio (for Claude Desktop) and HTTP (for web clients)
8
+ - **Six Search Tools**:
9
+ - `query` - Hybrid search (FTS + Vector + Reranking)
10
+ - `lex_search` - BM25 full-text search
11
+ - `vec_search` - Vector semantic search
12
+ - `get` - Get document by path or doc_id
13
+ - `multi_get` - Batch get documents by pattern
14
+ - `status` - Get index status
15
+ - **JSON-RPC 2.0 Protocol**: Full compliance with MCP specification
16
+ - **Server-Sent Events**: Real-time streaming for HTTP transport
17
+ - **FastAPI Integration**: Modern async web framework
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install fastapi uvicorn pydantic
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Stdio Mode (Claude Desktop)
28
+
29
+ ```python
30
+ from sif.mcp.transport_stdio import run_stdio
31
+ run_stdio()
32
+ ```
33
+
34
+ Or run directly:
35
+ ```bash
36
+ python -m sif.mcp.transport_stdio
37
+ ```
38
+
39
+ ### HTTP Mode
40
+
41
+ ```python
42
+ from sif.mcp.transport_http import run_http_server
43
+
44
+ async def main():
45
+ await run_http_server(host="0.0.0.0", port=8080)
46
+
47
+ import asyncio
48
+ asyncio.run(main())
49
+ ```
50
+
51
+ Or run directly:
52
+ ```bash
53
+ python -m sif.mcp.transport_http --host 0.0.0.0 --port 8080
54
+ ```
55
+
56
+ ## API Endpoints (HTTP Mode)
57
+
58
+ | Endpoint | Method | Description |
59
+ |----------|--------|-------------|
60
+ | `/health` | GET | Health check |
61
+ | `/mcp/v1/sse` | GET | Server-Sent Events stream |
62
+ | `/mcp/v1/messages` | POST | JSON-RPC message endpoint |
63
+ | `/mcp/v1/batch` | POST | Batch JSON-RPC messages |
64
+ | `/mcp/v1/tools` | GET | List available tools |
65
+ | `/mcp/v1/status` | GET | Get server status |
66
+
67
+ ## Tools
68
+
69
+ ### query
70
+
71
+ Hybrid search combining full-text search (BM25), vector semantic search, and reranking.
72
+
73
+ **Input:**
74
+ ```json
75
+ {
76
+ "query": "search terms",
77
+ "collections": ["default"],
78
+ "limit": 10,
79
+ "min_score": 0.0
80
+ }
81
+ ```
82
+
83
+ **Output:**
84
+ ```json
85
+ {
86
+ "results": [
87
+ {
88
+ "doc_id": "doc_1",
89
+ "path": "/docs/file.md",
90
+ "title": "Document Title",
91
+ "content": "...",
92
+ "score": 0.95,
93
+ "highlights": ["..."],
94
+ "metadata": {}
95
+ }
96
+ ]
97
+ }
98
+ ```
99
+
100
+ ### lex_search
101
+
102
+ BM25 full-text search for keyword-based document retrieval.
103
+
104
+ **Input:**
105
+ ```json
106
+ {
107
+ "query": "search terms",
108
+ "collections": ["default"],
109
+ "limit": 10
110
+ }
111
+ ```
112
+
113
+ ### vec_search
114
+
115
+ Vector semantic search for finding documents with similar meaning.
116
+
117
+ **Input:**
118
+ ```json
119
+ {
120
+ "query": "search terms",
121
+ "collections": ["default"],
122
+ "limit": 10
123
+ }
124
+ ```
125
+
126
+ ### get
127
+
128
+ Get a document by its path or document ID.
129
+
130
+ **Input:**
131
+ ```json
132
+ {
133
+ "path_or_docid": "/docs/file.md",
134
+ "from_line": 0,
135
+ "max_lines": 100
136
+ }
137
+ ```
138
+
139
+ ### multi_get
140
+
141
+ Batch get documents matching a glob pattern.
142
+
143
+ **Input:**
144
+ ```json
145
+ {
146
+ "pattern": "/docs/*.md",
147
+ "max_bytes": 1048576
148
+ }
149
+ ```
150
+
151
+ ### status
152
+
153
+ Get the current status of all document collections.
154
+
155
+ **Input:** `{}`
156
+
157
+ **Output:**
158
+ ```json
159
+ {
160
+ "collections": [
161
+ {
162
+ "name": "default",
163
+ "document_count": 100,
164
+ "last_updated": "2024-01-01T00:00:00",
165
+ "size_bytes": 1024000
166
+ }
167
+ ],
168
+ "total_documents": 100
169
+ }
170
+ ```
171
+
172
+ ## Protocol Flow
173
+
174
+ 1. **Initialize**: Client sends `initialize` request
175
+ 2. **Ready**: Server responds with capabilities
176
+ 3. **Notification**: Client sends `notifications/initialized`
177
+ 4. **Tools/List**: Client requests available tools
178
+ 5. **Tools/Call**: Client calls tools as needed
179
+
180
+ ## Example JSON-RPC Messages
181
+
182
+ ### Initialize Request
183
+ ```json
184
+ {
185
+ "jsonrpc": "2.0",
186
+ "id": 1,
187
+ "method": "initialize",
188
+ "params": {
189
+ "protocolVersion": "2024-11-05",
190
+ "capabilities": {},
191
+ "clientInfo": {
192
+ "name": "claude-desktop",
193
+ "version": "1.0.0"
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### Tools/List Request
200
+ ```json
201
+ {
202
+ "jsonrpc": "2.0",
203
+ "id": 2,
204
+ "method": "tools/list",
205
+ "params": {}
206
+ }
207
+ ```
208
+
209
+ ### Tools/Call Request
210
+ ```json
211
+ {
212
+ "jsonrpc": "2.0",
213
+ "id": 3,
214
+ "method": "tools/call",
215
+ "params": {
216
+ "name": "query",
217
+ "arguments": {
218
+ "query": "machine learning",
219
+ "limit": 5
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Configuration
226
+
227
+ ### ServerConfig
228
+
229
+ ```python
230
+ from sif.mcp import ServerConfig
231
+
232
+ config = ServerConfig(
233
+ name="my-mcp-server",
234
+ version="1.0.0",
235
+ protocol_version="2024-11-05",
236
+ enable_logging=True,
237
+ max_concurrent_requests=100
238
+ )
239
+ ```
240
+
241
+ ### Custom Search Backend
242
+
243
+ ```python
244
+ from sif.mcp import ToolRegistry, SearchBackend
245
+
246
+ class MySearchBackend(SearchBackend):
247
+ async def hybrid_search(self, query, collections=None, limit=10, min_score=0.0):
248
+ # Your implementation
249
+ pass
250
+
251
+ # ... implement other methods
252
+
253
+ tool_registry = ToolRegistry(backend=MySearchBackend())
254
+ ```
255
+
256
+ ## License
257
+
258
+ MIT
sif/mcp/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """SIF MCP (Model Context Protocol) Server."""
2
+
3
+ from sif.mcp.server import MCPServer, run_stdio_server
4
+
5
+
6
+ __all__ = [
7
+ "MCPServer",
8
+ "run_stdio_server",
9
+ ]
@@ -0,0 +1,16 @@
1
+ {
2
+ "mcpServers": {
3
+ "sif": {
4
+ "command": "python",
5
+ "args": [
6
+ "-m",
7
+ "sif.mcp.transport_stdio"
8
+ ],
9
+ "env": {
10
+ "PYTHONPATH": "/path/to/sif/src"
11
+ },
12
+ "description": "SIF MCP Server for document search and retrieval",
13
+ "disabled": false
14
+ }
15
+ }
16
+ }
sif/mcp/cli.py ADDED
@@ -0,0 +1,92 @@
1
+ """
2
+ MCP Server CLI
3
+
4
+ Command-line interface for running the SIF MCP server.
5
+ """
6
+
7
+ import argparse
8
+ import asyncio
9
+ import logging
10
+ import sys
11
+
12
+ from .transport_http import run_http_server
13
+ from .transport_stdio import run_stdio
14
+
15
+
16
+ def setup_logging(level: str = "info"):
17
+ """Setup logging configuration."""
18
+ logging.basicConfig(
19
+ level=getattr(logging, level.upper()),
20
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
21
+ stream=sys.stderr,
22
+ )
23
+
24
+
25
+ def main():
26
+ """Main CLI entry point."""
27
+ parser = argparse.ArgumentParser(
28
+ description="SIF MCP Server",
29
+ formatter_class=argparse.RawDescriptionHelpFormatter,
30
+ epilog="""
31
+ Examples:
32
+ # Run in stdio mode (for Claude Desktop)
33
+ %(prog)s stdio
34
+
35
+ # Run in HTTP mode
36
+ %(prog)s http --host 0.0.0.0 --port 8080
37
+
38
+ # Run with debug logging
39
+ %(prog)s http --log-level debug
40
+ """,
41
+ )
42
+
43
+ subparsers = parser.add_subparsers(dest="command", help="Transport mode")
44
+
45
+ # Stdio mode
46
+ stdio_parser = subparsers.add_parser("stdio", help="Run in stdio mode (for Claude Desktop)")
47
+ stdio_parser.add_argument(
48
+ "--log-level",
49
+ default="info",
50
+ choices=["debug", "info", "warning", "error"],
51
+ help="Log level (default: info)",
52
+ )
53
+
54
+ # HTTP mode
55
+ http_parser = subparsers.add_parser("http", help="Run in HTTP mode")
56
+ http_parser.add_argument("--host", default="0.0.0.0", help="Host to bind to (default: 0.0.0.0)")
57
+ http_parser.add_argument(
58
+ "--port", type=int, default=8080, help="Port to bind to (default: 8080)"
59
+ )
60
+ http_parser.add_argument(
61
+ "--log-level",
62
+ default="info",
63
+ choices=["debug", "info", "warning", "error"],
64
+ help="Log level (default: info)",
65
+ )
66
+ http_parser.add_argument(
67
+ "--cors-origins", nargs="*", default=["*"], help="CORS allowed origins (default: *)"
68
+ )
69
+
70
+ args = parser.parse_args()
71
+
72
+ if not args.command:
73
+ parser.print_help()
74
+ sys.exit(1)
75
+
76
+ setup_logging(args.log_level)
77
+
78
+ if args.command == "stdio":
79
+ run_stdio()
80
+ elif args.command == "http":
81
+ asyncio.run(
82
+ run_http_server(
83
+ host=args.host,
84
+ port=args.port,
85
+ cors_origins=args.cors_origins,
86
+ log_level=args.log_level,
87
+ )
88
+ )
89
+
90
+
91
+ if __name__ == "__main__":
92
+ main()
sif/mcp/protocol.py ADDED
@@ -0,0 +1,336 @@
1
+ """
2
+ MCP (Model Context Protocol) Protocol Definitions
3
+
4
+ This module defines the message types and protocol structures for MCP,
5
+ following the JSON-RPC 2.0 specification.
6
+
7
+ Reference: https://modelcontextprotocol.io/
8
+ """
9
+
10
+ from enum import Enum
11
+ from typing import Any, Literal, Optional
12
+
13
+ from pydantic import BaseModel, Field
14
+
15
+
16
+ # ============================================================================
17
+ # JSON-RPC Base Types
18
+ # ============================================================================
19
+
20
+
21
+ class JsonRpcVersion(str, Enum):
22
+ """JSON-RPC version."""
23
+
24
+ V2_0 = "2.0"
25
+
26
+
27
+ class JsonRpcRequest(BaseModel):
28
+ """JSON-RPC 2.0 Request object."""
29
+
30
+ jsonrpc: Literal["2.0"] = "2.0"
31
+ id: Optional[str | int] = None
32
+ method: str
33
+ params: Optional[dict[str, Any]] = None
34
+
35
+
36
+ class JsonRpcError(BaseModel):
37
+ """JSON-RPC 2.0 Error object."""
38
+
39
+ code: int
40
+ message: str
41
+ data: Optional[Any] = None
42
+
43
+
44
+ class JsonRpcResponse(BaseModel):
45
+ """JSON-RPC 2.0 Response object."""
46
+
47
+ jsonrpc: Literal["2.0"] = "2.0"
48
+ id: Optional[str | int] = None
49
+ result: Optional[Any] = None
50
+ error: Optional[JsonRpcError] = None
51
+
52
+
53
+ class JsonRpcNotification(BaseModel):
54
+ """JSON-RPC 2.0 Notification object (no id)."""
55
+
56
+ jsonrpc: Literal["2.0"] = "2.0"
57
+ method: str
58
+ params: Optional[dict[str, Any]] = None
59
+
60
+
61
+ # ============================================================================
62
+ # MCP Protocol Types
63
+ # ============================================================================
64
+
65
+
66
+ class MCPErrorCode(int, Enum):
67
+ """MCP-specific error codes."""
68
+
69
+ # Standard JSON-RPC errors
70
+ PARSE_ERROR = -32700
71
+ INVALID_REQUEST = -32600
72
+ METHOD_NOT_FOUND = -32601
73
+ INVALID_PARAMS = -32602
74
+ INTERNAL_ERROR = -32603
75
+
76
+ # MCP-specific errors
77
+ SERVER_NOT_INITIALIZED = -32002
78
+ UNKNOWN_TOOL = -32601
79
+ TOOL_EXECUTION_ERROR = -32000
80
+
81
+
82
+ # ============================================================================
83
+ # Initialize
84
+ # ============================================================================
85
+
86
+
87
+ class InitializeParams(BaseModel):
88
+ """Parameters for initialize request."""
89
+
90
+ protocolVersion: str = "2024-11-05"
91
+ capabilities: dict[str, Any] = Field(default_factory=dict)
92
+ clientInfo: dict[str, str] = Field(default_factory=dict)
93
+
94
+
95
+ class ServerCapabilities(BaseModel):
96
+ """Server capabilities."""
97
+
98
+ tools: dict[str, Any] = Field(default_factory=lambda: {"listChanged": False})
99
+
100
+
101
+ class InitializeResult(BaseModel):
102
+ """Result for initialize request."""
103
+
104
+ protocolVersion: str = "2024-11-05"
105
+ capabilities: ServerCapabilities = Field(default_factory=ServerCapabilities)
106
+ serverInfo: dict[str, str] = Field(
107
+ default_factory=lambda: {"name": "sif-mcp-server", "version": "1.0.0"}
108
+ )
109
+
110
+
111
+ # ============================================================================
112
+ # Tools
113
+ # ============================================================================
114
+
115
+
116
+ class ToolInputSchema(BaseModel):
117
+ """JSON Schema for tool input."""
118
+
119
+ type: str = "object"
120
+ properties: dict[str, Any] = Field(default_factory=dict)
121
+ required: list[str] = Field(default_factory=list)
122
+
123
+
124
+ class MCPTool(BaseModel):
125
+ """MCP Tool definition."""
126
+
127
+ name: str
128
+ description: str
129
+ inputSchema: ToolInputSchema = Field(default_factory=ToolInputSchema)
130
+
131
+
132
+ class ToolsListParams(BaseModel):
133
+ """Parameters for tools/list request."""
134
+
135
+ cursor: Optional[str] = None
136
+
137
+
138
+ class ToolsListResult(BaseModel):
139
+ """Result for tools/list request."""
140
+
141
+ tools: list[MCPTool] = Field(default_factory=list)
142
+ nextCursor: Optional[str] = None
143
+
144
+
145
+ class ToolsCallParams(BaseModel):
146
+ """Parameters for tools/call request."""
147
+
148
+ name: str
149
+ arguments: dict[str, Any] = Field(default_factory=dict)
150
+
151
+
152
+ class ToolContentItem(BaseModel):
153
+ """Content item in tool call result."""
154
+
155
+ type: Literal["text", "image", "resource"] = "text"
156
+ text: Optional[str] = None
157
+ data: Optional[str] = None # For image base64 data
158
+ mimeType: Optional[str] = None
159
+
160
+
161
+ class ToolsCallResult(BaseModel):
162
+ """Result for tools/call request."""
163
+
164
+ content: list[ToolContentItem] = Field(default_factory=list)
165
+ isError: bool = False
166
+
167
+
168
+ # ============================================================================
169
+ # Notifications
170
+ # ============================================================================
171
+
172
+
173
+ class InitializedNotification(BaseModel):
174
+ """Client notification after initialization."""
175
+
176
+ method: Literal["notifications/initialized"] = "notifications/initialized"
177
+ params: dict[str, Any] = Field(default_factory=dict)
178
+
179
+
180
+ class ProgressNotification(BaseModel):
181
+ """Progress notification."""
182
+
183
+ method: Literal["notifications/progress"] = "notifications/progress"
184
+ params: dict[str, Any]
185
+
186
+
187
+ # ============================================================================
188
+ # SIF-specific Types
189
+ # ============================================================================
190
+
191
+
192
+ class SearchResult(BaseModel):
193
+ """Search result item."""
194
+
195
+ doc_id: str
196
+ path: str
197
+ title: Optional[str] = None
198
+ content: Optional[str] = None
199
+ score: float
200
+ highlights: list[str] = Field(default_factory=list)
201
+ metadata: dict[str, Any] = Field(default_factory=dict)
202
+
203
+
204
+ class Document(BaseModel):
205
+ """Document model."""
206
+
207
+ doc_id: str
208
+ path: str
209
+ title: Optional[str] = None
210
+ content: str
211
+ metadata: dict[str, Any] = Field(default_factory=dict)
212
+ line_start: Optional[int] = None
213
+ line_end: Optional[int] = None
214
+
215
+
216
+ class CollectionInfo(BaseModel):
217
+ """Collection information."""
218
+
219
+ name: str
220
+ document_count: int
221
+ last_updated: Optional[str] = None
222
+ size_bytes: Optional[int] = None
223
+
224
+
225
+ # ============================================================================
226
+ # Tool Input/Output Models
227
+ # ============================================================================
228
+
229
+
230
+ class QueryInput(BaseModel):
231
+ """Input for query tool."""
232
+
233
+ query: str
234
+ collections: Optional[list[str]] = None
235
+ limit: int = 10
236
+ min_score: float = 0.0
237
+
238
+
239
+ class QueryOutput(BaseModel):
240
+ """Output for query tool."""
241
+
242
+ results: list[SearchResult] = Field(default_factory=list)
243
+
244
+
245
+ class LexSearchInput(BaseModel):
246
+ """Input for lex_search tool."""
247
+
248
+ query: str
249
+ collections: Optional[list[str]] = None
250
+ limit: int = 10
251
+
252
+
253
+ class LexSearchOutput(BaseModel):
254
+ """Output for lex_search tool."""
255
+
256
+ results: list[SearchResult] = Field(default_factory=list)
257
+
258
+
259
+ class VecSearchInput(BaseModel):
260
+ """Input for vec_search tool."""
261
+
262
+ query: str
263
+ collections: Optional[list[str]] = None
264
+ limit: int = 10
265
+
266
+
267
+ class VecSearchOutput(BaseModel):
268
+ """Output for vec_search tool."""
269
+
270
+ results: list[SearchResult] = Field(default_factory=list)
271
+
272
+
273
+ class GetInput(BaseModel):
274
+ """Input for get tool."""
275
+
276
+ path_or_docid: str
277
+ from_line: Optional[int] = None
278
+ max_lines: Optional[int] = None
279
+
280
+
281
+ class GetOutput(BaseModel):
282
+ """Output for get tool."""
283
+
284
+ document: Optional[Document] = None
285
+
286
+
287
+ class MultiGetInput(BaseModel):
288
+ """Input for multi_get tool."""
289
+
290
+ pattern: str
291
+ max_bytes: Optional[int] = None
292
+
293
+
294
+ class MultiGetOutput(BaseModel):
295
+ """Output for multi_get tool."""
296
+
297
+ documents: list[Document] = Field(default_factory=list)
298
+ errors: list[str] = Field(default_factory=list)
299
+
300
+
301
+ class StatusInput(BaseModel):
302
+ """Input for status tool."""
303
+
304
+ pass
305
+
306
+
307
+ class StatusOutput(BaseModel):
308
+ """Output for status tool."""
309
+
310
+ collections: list[CollectionInfo] = Field(default_factory=list)
311
+ total_documents: int = 0
312
+
313
+
314
+ # ============================================================================
315
+ # Helper Functions
316
+ # ============================================================================
317
+
318
+
319
+ def create_success_response(id: str | int | None, result: Any) -> JsonRpcResponse: # noqa: A002
320
+ """Create a successful JSON-RPC response."""
321
+ return JsonRpcResponse(id=id, result=result)
322
+
323
+
324
+ def create_error_response(
325
+ id: str | int | None,
326
+ code: int,
327
+ message: str,
328
+ data: Any = None, # noqa: A002
329
+ ) -> JsonRpcResponse:
330
+ """Create an error JSON-RPC response."""
331
+ return JsonRpcResponse(id=id, error=JsonRpcError(code=code, message=message, data=data))
332
+
333
+
334
+ def create_notification(method: str, params: dict[str, Any] | None = None) -> JsonRpcNotification:
335
+ """Create a JSON-RPC notification."""
336
+ return JsonRpcNotification(method=method, params=params or {})