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.
- docsif-0.1.0.dist-info/METADATA +393 -0
- docsif-0.1.0.dist-info/RECORD +86 -0
- docsif-0.1.0.dist-info/WHEEL +4 -0
- docsif-0.1.0.dist-info/entry_points.txt +3 -0
- docsif-0.1.0.dist-info/licenses/LICENSE +21 -0
- sif/__init__.py +24 -0
- sif/_version.py +3 -0
- sif/cli/__init__.py +11 -0
- sif/cli/commands/__init__.py +18 -0
- sif/cli/commands/bench.py +148 -0
- sif/cli/commands/collection.py +360 -0
- sif/cli/commands/context.py +154 -0
- sif/cli/commands/get.py +200 -0
- sif/cli/commands/index.py +294 -0
- sif/cli/commands/ls.py +67 -0
- sif/cli/commands/mcp.py +93 -0
- sif/cli/commands/pull.py +111 -0
- sif/cli/commands/search.py +574 -0
- sif/cli/config.py +206 -0
- sif/cli/formatters.py +269 -0
- sif/cli/main.py +205 -0
- sif/config/__init__.py +30 -0
- sif/config/constants.py +52 -0
- sif/config/settings.py +217 -0
- sif/core/__init__.py +21 -0
- sif/core/collection.py +161 -0
- sif/core/context.py +167 -0
- sif/core/document.py +125 -0
- sif/core/models.py +298 -0
- sif/database/__init__.py +23 -0
- sif/database/connection.py +139 -0
- sif/database/database.py +136 -0
- sif/database/migrations.py +187 -0
- sif/database/repositories.py +500 -0
- sif/database/repository.py +171 -0
- sif/database/schema.py +362 -0
- sif/embedding/__init__.py +22 -0
- sif/embedding/cache.py +196 -0
- sif/embedding/embedder.py +328 -0
- sif/embedding/factory.py +92 -0
- sif/embedding/manager.py +200 -0
- sif/embedding/model.py +126 -0
- sif/indexing/__init__.py +21 -0
- sif/indexing/chunker.py +226 -0
- sif/indexing/indexer.py +226 -0
- sif/indexing/parser.py +192 -0
- sif/indexing/scanner.py +148 -0
- sif/indexing/watcher.py +156 -0
- sif/mcp/README.md +258 -0
- sif/mcp/__init__.py +9 -0
- sif/mcp/claude_desktop_config.json +16 -0
- sif/mcp/cli.py +92 -0
- sif/mcp/protocol.py +336 -0
- sif/mcp/server.py +433 -0
- sif/mcp/server_http.py +54 -0
- sif/mcp/test_mcp.py +108 -0
- sif/mcp/tools.py +497 -0
- sif/mcp/transport_http.py +424 -0
- sif/mcp/transport_stdio.py +292 -0
- sif/mcp_server/__init__.py +26 -0
- sif/mcp_server/handlers.py +68 -0
- sif/mcp_server/server.py +58 -0
- sif/mcp_server/tools.py +216 -0
- sif/mcp_server/transport.py +221 -0
- sif/models/__init__.py +70 -0
- sif/models/collection.py +51 -0
- sif/models/context.py +45 -0
- sif/models/document.py +76 -0
- sif/models/download.py +208 -0
- sif/models/embedding.py +71 -0
- sif/models/search.py +92 -0
- sif/search/__init__.py +22 -0
- sif/search/benchmark.py +110 -0
- sif/search/bm25.py +216 -0
- sif/search/expansion.py +157 -0
- sif/search/hybrid.py +323 -0
- sif/search/rerank.py +390 -0
- sif/search/rrf.py +186 -0
- sif/search/snippets.py +130 -0
- sif/search/strategy.py +97 -0
- sif/search/vector.py +177 -0
- sif/utils/__init__.py +26 -0
- sif/utils/logging.py +143 -0
- sif/utils/paths.py +82 -0
- sif/utils/progress.py +83 -0
- 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,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 {})
|