one-ctx 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.
- one_ctx-0.1.0/.gitignore +35 -0
- one_ctx-0.1.0/LICENSE +21 -0
- one_ctx-0.1.0/PKG-INFO +182 -0
- one_ctx-0.1.0/README.md +167 -0
- one_ctx-0.1.0/_check_names.py +10 -0
- one_ctx-0.1.0/ctx/__init__.py +1 -0
- one_ctx-0.1.0/ctx/cli.py +159 -0
- one_ctx-0.1.0/ctx/database.py +292 -0
- one_ctx-0.1.0/ctx/git.py +106 -0
- one_ctx-0.1.0/ctx/llm.py +416 -0
- one_ctx-0.1.0/ctx/server.py +357 -0
- one_ctx-0.1.0/pyproject.toml +26 -0
- one_ctx-0.1.0/run_ctx.bat +2 -0
one_ctx-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
|
|
15
|
+
# Database
|
|
16
|
+
*.db
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.idea/
|
|
20
|
+
.vscode/
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# Test artifacts
|
|
29
|
+
test_mcp.py
|
|
30
|
+
|
|
31
|
+
# Debug files
|
|
32
|
+
mcp_debug.log
|
|
33
|
+
claude_debug.log
|
|
34
|
+
run_ctx_debug.bat
|
|
35
|
+
run_claude_debug.bat
|
one_ctx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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.
|
one_ctx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: one-ctx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Combined Context — One MCP server. Every AI tool. No re-explaining.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: click>=8.1.0
|
|
9
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
10
|
+
Requires-Dist: sse-starlette>=2.0.0
|
|
11
|
+
Requires-Dist: uvicorn[standard]>=0.29.0
|
|
12
|
+
Provides-Extra: llm
|
|
13
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'llm'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# one-context — Combined Context
|
|
17
|
+
|
|
18
|
+
> One MCP server. Every AI tool. No re-explaining.
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
Claude Code, Cline, Antigravity, Codex, Ollama — they all point to the same place.
|
|
23
|
+
You explain your project once. Every tool knows it forever.
|
|
24
|
+
|
|
25
|
+
**No API keys needed. No cloud. No accounts. 100% local.**
|
|
26
|
+
|
|
27
|
+
## The Architecture
|
|
28
|
+
|
|
29
|
+
Every AI tool keeps its own internal memory. That doesn't change. But they all **read and write to one shared root**:
|
|
30
|
+
|
|
31
|
+
```mermaid
|
|
32
|
+
graph TD
|
|
33
|
+
%% Define styles
|
|
34
|
+
classDef rootNode fill:#0d1117,stroke:#58a6ff,stroke-width:3px,color:#c9d1d9,font-size:16px,font-weight:bold;
|
|
35
|
+
classDef aiNode fill:#161b22,stroke:#3fb950,stroke-width:2px,color:#c9d1d9,font-size:14px;
|
|
36
|
+
classDef bucketNode fill:#21262d,stroke:#8b949e,stroke-dasharray: 5 5,color:#c9d1d9;
|
|
37
|
+
|
|
38
|
+
%% Nodes
|
|
39
|
+
A([one-context MCP]):::rootNode
|
|
40
|
+
|
|
41
|
+
C1[Claude Code]:::aiNode
|
|
42
|
+
C2[Cline]:::aiNode
|
|
43
|
+
C3[Antigravity]:::aiNode
|
|
44
|
+
C4[Codex]:::aiNode
|
|
45
|
+
|
|
46
|
+
B1[(WHAT\nProject Scope)]:::bucketNode
|
|
47
|
+
B2[(DONE\nHistory)]:::bucketNode
|
|
48
|
+
B3[(NOW\nCurrent Task)]:::bucketNode
|
|
49
|
+
B4[(MAP\nKey Files)]:::bucketNode
|
|
50
|
+
|
|
51
|
+
%% Connections
|
|
52
|
+
C1 <-->|reads/writes| A
|
|
53
|
+
C2 <-->|reads/writes| A
|
|
54
|
+
C3 <-->|reads/writes| A
|
|
55
|
+
C4 <-->|reads/writes| A
|
|
56
|
+
|
|
57
|
+
A --- B1
|
|
58
|
+
A --- B2
|
|
59
|
+
A --- B3
|
|
60
|
+
A --- B4
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
With `uv` installed, you don't even need to download this repository. Your AI tools will fetch it automatically from PyPI!
|
|
66
|
+
|
|
67
|
+
## Connect Your AI Tools
|
|
68
|
+
|
|
69
|
+
### Option A: Command/stdio (recommended)
|
|
70
|
+
|
|
71
|
+
Works with Claude Desktop, Cline, Codex, and any MCP client. Just add this to your MCP settings file:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mcpServers": {
|
|
76
|
+
"one-context": {
|
|
77
|
+
"command": "uvx",
|
|
78
|
+
"args": ["one-context", "stdio"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
No server to start. The AI tool launches it automatically.
|
|
85
|
+
|
|
86
|
+
### Option B: HTTP/SSE (for network setups)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uvx one-context serve
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then point any MCP client to `http://localhost:7337/sse`.
|
|
93
|
+
|
|
94
|
+
## The Four Buckets
|
|
95
|
+
|
|
96
|
+
| Bucket | Contains | Updated when... |
|
|
97
|
+
|--------|----------|----------------|
|
|
98
|
+
| **WHAT** | Project description, stack, architecture, constraints | Project-level info changes |
|
|
99
|
+
| **DONE** | Decisions made, files changed, problems solved | Any tool finishes a task |
|
|
100
|
+
| **NOW** | Current task, current state, what's in progress | A new task starts |
|
|
101
|
+
| **MAP** | Important file paths and what they do | AI discovers key files |
|
|
102
|
+
|
|
103
|
+
## Usage in any tool
|
|
104
|
+
|
|
105
|
+
Start a session:
|
|
106
|
+
```
|
|
107
|
+
"Load context from one-context for my-project"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Finish a session:
|
|
111
|
+
```
|
|
112
|
+
"Update one-context with what we just did"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Register important files:
|
|
116
|
+
```
|
|
117
|
+
"Use ctx_map to register src/main.py as the entry point"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Search across all your projects:
|
|
121
|
+
```
|
|
122
|
+
"Search one-context for 'SQLite lock error'"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## MCP Tools (6 total)
|
|
126
|
+
|
|
127
|
+
| Tool | Description |
|
|
128
|
+
|------|-------------|
|
|
129
|
+
| `ctx_get(project)` | Get the full WHAT/DONE/NOW/MAP snapshot + git info |
|
|
130
|
+
| `ctx_update(project, session_summary, tool_name)` | Merge a session update into context |
|
|
131
|
+
| `ctx_map(project, files)` | Register important files manually |
|
|
132
|
+
| `ctx_search(query)` | Search across ALL projects' context and history |
|
|
133
|
+
| `ctx_reset(project)` | Wipe project context to empty |
|
|
134
|
+
| `ctx_list()` | List all tracked projects |
|
|
135
|
+
|
|
136
|
+
Projects are auto-created on first `ctx_update` — you don't need `ctx init`.
|
|
137
|
+
|
|
138
|
+
## Advanced Features
|
|
139
|
+
|
|
140
|
+
### MAP — Important Files
|
|
141
|
+
When an AI tool discovers that `src/main.py` is the entry point, it saves those paths to the MAP bucket. The next AI tool instantly knows which files matter without scanning the entire codebase.
|
|
142
|
+
|
|
143
|
+
### Git Branch Awareness
|
|
144
|
+
Link a project to its git repo:
|
|
145
|
+
```bash
|
|
146
|
+
ctx init my-project --path /path/to/repo
|
|
147
|
+
```
|
|
148
|
+
Now `ctx_get` automatically includes the current branch name, last 5 commits, and uncommitted changes.
|
|
149
|
+
|
|
150
|
+
### Cross-Project Search
|
|
151
|
+
```bash
|
|
152
|
+
ctx search "SQLite"
|
|
153
|
+
```
|
|
154
|
+
Searches across ALL projects' context (what/done/now/map) and update history. Find how you solved a problem before.
|
|
155
|
+
|
|
156
|
+
## Summarization Modes
|
|
157
|
+
|
|
158
|
+
| Mode | Setup | API Key? |
|
|
159
|
+
|------|-------|----------|
|
|
160
|
+
| **Local** (default) | Nothing | No |
|
|
161
|
+
| **Ollama** | `CTX_OLLAMA_MODEL=llama3.2` | No |
|
|
162
|
+
| **Claude** | `ANTHROPIC_API_KEY=...` | Yes |
|
|
163
|
+
| **OpenAI / Groq / Together** | `OPENAI_API_KEY=...` | Yes |
|
|
164
|
+
|
|
165
|
+
Priority: Ollama > Claude > OpenAI > Local fallback. If any provider fails, it silently falls back. **Local always works.**
|
|
166
|
+
|
|
167
|
+
## CLI Commands
|
|
168
|
+
|
|
169
|
+
| Command | Description |
|
|
170
|
+
|---------|-------------|
|
|
171
|
+
| `ctx stdio` | Run via stdio (for MCP clients) |
|
|
172
|
+
| `ctx serve` | Start HTTP/SSE server |
|
|
173
|
+
| `ctx init <project> [--path /repo]` | Initialize with optional git repo |
|
|
174
|
+
| `ctx status [project]` | Show context + git info |
|
|
175
|
+
| `ctx search <query>` | Search across all projects |
|
|
176
|
+
| `ctx reset <project>` | Reset project context |
|
|
177
|
+
| `ctx list` | List all projects |
|
|
178
|
+
| `ctx delete <project>` | Delete a project |
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
one_ctx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# one-context — Combined Context
|
|
2
|
+
|
|
3
|
+
> One MCP server. Every AI tool. No re-explaining.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Claude Code, Cline, Antigravity, Codex, Ollama — they all point to the same place.
|
|
8
|
+
You explain your project once. Every tool knows it forever.
|
|
9
|
+
|
|
10
|
+
**No API keys needed. No cloud. No accounts. 100% local.**
|
|
11
|
+
|
|
12
|
+
## The Architecture
|
|
13
|
+
|
|
14
|
+
Every AI tool keeps its own internal memory. That doesn't change. But they all **read and write to one shared root**:
|
|
15
|
+
|
|
16
|
+
```mermaid
|
|
17
|
+
graph TD
|
|
18
|
+
%% Define styles
|
|
19
|
+
classDef rootNode fill:#0d1117,stroke:#58a6ff,stroke-width:3px,color:#c9d1d9,font-size:16px,font-weight:bold;
|
|
20
|
+
classDef aiNode fill:#161b22,stroke:#3fb950,stroke-width:2px,color:#c9d1d9,font-size:14px;
|
|
21
|
+
classDef bucketNode fill:#21262d,stroke:#8b949e,stroke-dasharray: 5 5,color:#c9d1d9;
|
|
22
|
+
|
|
23
|
+
%% Nodes
|
|
24
|
+
A([one-context MCP]):::rootNode
|
|
25
|
+
|
|
26
|
+
C1[Claude Code]:::aiNode
|
|
27
|
+
C2[Cline]:::aiNode
|
|
28
|
+
C3[Antigravity]:::aiNode
|
|
29
|
+
C4[Codex]:::aiNode
|
|
30
|
+
|
|
31
|
+
B1[(WHAT\nProject Scope)]:::bucketNode
|
|
32
|
+
B2[(DONE\nHistory)]:::bucketNode
|
|
33
|
+
B3[(NOW\nCurrent Task)]:::bucketNode
|
|
34
|
+
B4[(MAP\nKey Files)]:::bucketNode
|
|
35
|
+
|
|
36
|
+
%% Connections
|
|
37
|
+
C1 <-->|reads/writes| A
|
|
38
|
+
C2 <-->|reads/writes| A
|
|
39
|
+
C3 <-->|reads/writes| A
|
|
40
|
+
C4 <-->|reads/writes| A
|
|
41
|
+
|
|
42
|
+
A --- B1
|
|
43
|
+
A --- B2
|
|
44
|
+
A --- B3
|
|
45
|
+
A --- B4
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
With `uv` installed, you don't even need to download this repository. Your AI tools will fetch it automatically from PyPI!
|
|
51
|
+
|
|
52
|
+
## Connect Your AI Tools
|
|
53
|
+
|
|
54
|
+
### Option A: Command/stdio (recommended)
|
|
55
|
+
|
|
56
|
+
Works with Claude Desktop, Cline, Codex, and any MCP client. Just add this to your MCP settings file:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"one-context": {
|
|
62
|
+
"command": "uvx",
|
|
63
|
+
"args": ["one-context", "stdio"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
No server to start. The AI tool launches it automatically.
|
|
70
|
+
|
|
71
|
+
### Option B: HTTP/SSE (for network setups)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
uvx one-context serve
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then point any MCP client to `http://localhost:7337/sse`.
|
|
78
|
+
|
|
79
|
+
## The Four Buckets
|
|
80
|
+
|
|
81
|
+
| Bucket | Contains | Updated when... |
|
|
82
|
+
|--------|----------|----------------|
|
|
83
|
+
| **WHAT** | Project description, stack, architecture, constraints | Project-level info changes |
|
|
84
|
+
| **DONE** | Decisions made, files changed, problems solved | Any tool finishes a task |
|
|
85
|
+
| **NOW** | Current task, current state, what's in progress | A new task starts |
|
|
86
|
+
| **MAP** | Important file paths and what they do | AI discovers key files |
|
|
87
|
+
|
|
88
|
+
## Usage in any tool
|
|
89
|
+
|
|
90
|
+
Start a session:
|
|
91
|
+
```
|
|
92
|
+
"Load context from one-context for my-project"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Finish a session:
|
|
96
|
+
```
|
|
97
|
+
"Update one-context with what we just did"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Register important files:
|
|
101
|
+
```
|
|
102
|
+
"Use ctx_map to register src/main.py as the entry point"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Search across all your projects:
|
|
106
|
+
```
|
|
107
|
+
"Search one-context for 'SQLite lock error'"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## MCP Tools (6 total)
|
|
111
|
+
|
|
112
|
+
| Tool | Description |
|
|
113
|
+
|------|-------------|
|
|
114
|
+
| `ctx_get(project)` | Get the full WHAT/DONE/NOW/MAP snapshot + git info |
|
|
115
|
+
| `ctx_update(project, session_summary, tool_name)` | Merge a session update into context |
|
|
116
|
+
| `ctx_map(project, files)` | Register important files manually |
|
|
117
|
+
| `ctx_search(query)` | Search across ALL projects' context and history |
|
|
118
|
+
| `ctx_reset(project)` | Wipe project context to empty |
|
|
119
|
+
| `ctx_list()` | List all tracked projects |
|
|
120
|
+
|
|
121
|
+
Projects are auto-created on first `ctx_update` — you don't need `ctx init`.
|
|
122
|
+
|
|
123
|
+
## Advanced Features
|
|
124
|
+
|
|
125
|
+
### MAP — Important Files
|
|
126
|
+
When an AI tool discovers that `src/main.py` is the entry point, it saves those paths to the MAP bucket. The next AI tool instantly knows which files matter without scanning the entire codebase.
|
|
127
|
+
|
|
128
|
+
### Git Branch Awareness
|
|
129
|
+
Link a project to its git repo:
|
|
130
|
+
```bash
|
|
131
|
+
ctx init my-project --path /path/to/repo
|
|
132
|
+
```
|
|
133
|
+
Now `ctx_get` automatically includes the current branch name, last 5 commits, and uncommitted changes.
|
|
134
|
+
|
|
135
|
+
### Cross-Project Search
|
|
136
|
+
```bash
|
|
137
|
+
ctx search "SQLite"
|
|
138
|
+
```
|
|
139
|
+
Searches across ALL projects' context (what/done/now/map) and update history. Find how you solved a problem before.
|
|
140
|
+
|
|
141
|
+
## Summarization Modes
|
|
142
|
+
|
|
143
|
+
| Mode | Setup | API Key? |
|
|
144
|
+
|------|-------|----------|
|
|
145
|
+
| **Local** (default) | Nothing | No |
|
|
146
|
+
| **Ollama** | `CTX_OLLAMA_MODEL=llama3.2` | No |
|
|
147
|
+
| **Claude** | `ANTHROPIC_API_KEY=...` | Yes |
|
|
148
|
+
| **OpenAI / Groq / Together** | `OPENAI_API_KEY=...` | Yes |
|
|
149
|
+
|
|
150
|
+
Priority: Ollama > Claude > OpenAI > Local fallback. If any provider fails, it silently falls back. **Local always works.**
|
|
151
|
+
|
|
152
|
+
## CLI Commands
|
|
153
|
+
|
|
154
|
+
| Command | Description |
|
|
155
|
+
|---------|-------------|
|
|
156
|
+
| `ctx stdio` | Run via stdio (for MCP clients) |
|
|
157
|
+
| `ctx serve` | Start HTTP/SSE server |
|
|
158
|
+
| `ctx init <project> [--path /repo]` | Initialize with optional git repo |
|
|
159
|
+
| `ctx status [project]` | Show context + git info |
|
|
160
|
+
| `ctx search <query>` | Search across all projects |
|
|
161
|
+
| `ctx reset <project>` | Reset project context |
|
|
162
|
+
| `ctx list` | List all projects |
|
|
163
|
+
| `ctx delete <project>` | Delete a project |
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import urllib.request
|
|
2
|
+
from urllib.error import HTTPError
|
|
3
|
+
|
|
4
|
+
names = ['one-ctx', 'octx-mcp', 'ctx-mcp', 'onectx', 'mcp-ctx']
|
|
5
|
+
for n in names:
|
|
6
|
+
try:
|
|
7
|
+
urllib.request.urlopen(f'https://pypi.org/pypi/{n}/json')
|
|
8
|
+
print(f'{n}: TAKEN')
|
|
9
|
+
except HTTPError:
|
|
10
|
+
print(f'{n}: AVAILABLE')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ctx — Combined Context MCP Server."""
|
one_ctx-0.1.0/ctx/cli.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
if sys.platform == 'win32':
|
|
3
|
+
import asyncio
|
|
4
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
5
|
+
import sys
|
|
6
|
+
import anyio
|
|
7
|
+
import click
|
|
8
|
+
from ctx.database import (
|
|
9
|
+
init_project, get_project, reset_project, list_projects,
|
|
10
|
+
search_projects, search_logs,
|
|
11
|
+
)
|
|
12
|
+
from ctx.server import mcp_server
|
|
13
|
+
from ctx.git import get_git_summary
|
|
14
|
+
import mcp.server.stdio
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def cli():
|
|
18
|
+
"""ctx -- Combined Context MCP Server."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@cli.command()
|
|
22
|
+
@click.option("--port", default=7337, help="Port to run the server on")
|
|
23
|
+
@click.option("--host", default="0.0.0.0", help="Host to bind to")
|
|
24
|
+
def serve(port, host):
|
|
25
|
+
"""Start the HTTP/SSE server."""
|
|
26
|
+
import uvicorn
|
|
27
|
+
print(f"[ctx] server starting on http://{host}:{port}")
|
|
28
|
+
print(f" SSE endpoint: http://localhost:{port}/sse")
|
|
29
|
+
print(f" Messages endpoint: http://localhost:{port}/messages/")
|
|
30
|
+
print(f" Health check: http://localhost:{port}/health\n")
|
|
31
|
+
print("Add to your MCP config:")
|
|
32
|
+
print(f' {{"mcpServers": {{"ctx": {{"url": "http://localhost:{port}/sse"}}}}}}\n')
|
|
33
|
+
|
|
34
|
+
uvicorn.run("ctx.server:app", host=host, port=port, log_level="info")
|
|
35
|
+
|
|
36
|
+
@cli.command()
|
|
37
|
+
def stdio():
|
|
38
|
+
"""Start the stdio server (for direct VS Code / Cline / Codex integration)."""
|
|
39
|
+
async def run():
|
|
40
|
+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
41
|
+
await mcp_server.run(
|
|
42
|
+
read_stream,
|
|
43
|
+
write_stream,
|
|
44
|
+
mcp_server.create_initialization_options()
|
|
45
|
+
)
|
|
46
|
+
anyio.run(run)
|
|
47
|
+
|
|
48
|
+
@cli.command()
|
|
49
|
+
@click.argument("project")
|
|
50
|
+
@click.option("--path", default="", help="Path to the project's git repo on disk")
|
|
51
|
+
def init(project, path):
|
|
52
|
+
"""Initialize a new project context."""
|
|
53
|
+
result = init_project(project, repo_path=path)
|
|
54
|
+
if "error" in result:
|
|
55
|
+
print(f"[!] {result['error']}")
|
|
56
|
+
return
|
|
57
|
+
print(f"[OK] Project '{project}' initialized.")
|
|
58
|
+
if path:
|
|
59
|
+
print(f" Git repo linked: {path}")
|
|
60
|
+
print(" Start any AI tool and call ctx_get to load context.")
|
|
61
|
+
|
|
62
|
+
@cli.command()
|
|
63
|
+
@click.argument("project", required=False)
|
|
64
|
+
def status(project):
|
|
65
|
+
"""View the context for a project, or list all projects."""
|
|
66
|
+
if not project:
|
|
67
|
+
projects = list_projects()
|
|
68
|
+
if not projects:
|
|
69
|
+
print("No projects found.")
|
|
70
|
+
return
|
|
71
|
+
print("=== Tracked Projects ===")
|
|
72
|
+
for p in projects:
|
|
73
|
+
print(f"- {p['project']} (last updated: {p['updated_at']})")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
data = get_project(project)
|
|
77
|
+
if "error" in data:
|
|
78
|
+
print(f"Error: {data['error']}")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
print(f"=== {project} ===")
|
|
82
|
+
print(f"\n[WHAT]\n{data.get('what', '(empty)') or '(empty)'}")
|
|
83
|
+
print(f"\n[DONE]\n{data.get('done', '(empty)') or '(empty)'}")
|
|
84
|
+
print(f"\n[NOW]\n{data.get('now', '(empty)') or '(empty)'}")
|
|
85
|
+
print(f"\n[MAP]\n{data.get('map', '(empty)') or '(no files mapped)'}")
|
|
86
|
+
|
|
87
|
+
# Show git info if repo_path is set
|
|
88
|
+
repo_path = data.get("repo_path", "")
|
|
89
|
+
if repo_path:
|
|
90
|
+
print(f"\n[GIT] repo: {repo_path}")
|
|
91
|
+
git_info = get_git_summary(repo_path)
|
|
92
|
+
if git_info:
|
|
93
|
+
print(f" Branch: {git_info['branch']}")
|
|
94
|
+
if git_info.get("recent_commits"):
|
|
95
|
+
print(f" Recent commits:")
|
|
96
|
+
for c in git_info["recent_commits"][:3]:
|
|
97
|
+
print(f" {c['hash']} {c['message']}")
|
|
98
|
+
changed = git_info.get("changed_files", {})
|
|
99
|
+
total_changes = len(changed.get("staged", [])) + len(changed.get("unstaged", [])) + len(changed.get("untracked", []))
|
|
100
|
+
if total_changes:
|
|
101
|
+
print(f" Changed files: {total_changes}")
|
|
102
|
+
else:
|
|
103
|
+
print(" (git not available or not a repo)")
|
|
104
|
+
|
|
105
|
+
@cli.command()
|
|
106
|
+
@click.argument("project")
|
|
107
|
+
def reset(project):
|
|
108
|
+
"""Reset a project's context back to empty."""
|
|
109
|
+
reset_project(project)
|
|
110
|
+
print(f"[OK] Project '{project}' has been reset to empty.")
|
|
111
|
+
|
|
112
|
+
@cli.command(name="list")
|
|
113
|
+
def list_cmd():
|
|
114
|
+
"""List all tracked projects."""
|
|
115
|
+
projects = list_projects()
|
|
116
|
+
if not projects:
|
|
117
|
+
print("No projects found.")
|
|
118
|
+
return
|
|
119
|
+
print("=== Tracked Projects ===")
|
|
120
|
+
for p in projects:
|
|
121
|
+
print(f"- {p['project']} (last updated: {p['updated_at']})")
|
|
122
|
+
|
|
123
|
+
@cli.command()
|
|
124
|
+
@click.argument("project")
|
|
125
|
+
def delete(project):
|
|
126
|
+
"""Permanently delete a project."""
|
|
127
|
+
from ctx.database import delete_project
|
|
128
|
+
result = delete_project(project)
|
|
129
|
+
if "error" in result:
|
|
130
|
+
print(f"[!] {result['error']}")
|
|
131
|
+
else:
|
|
132
|
+
print(f"[OK] Project '{project}' deleted.")
|
|
133
|
+
|
|
134
|
+
@cli.command()
|
|
135
|
+
@click.argument("query")
|
|
136
|
+
def search(query):
|
|
137
|
+
"""Search across all projects' context and history."""
|
|
138
|
+
project_matches = search_projects(query)
|
|
139
|
+
log_matches = search_logs(query)
|
|
140
|
+
|
|
141
|
+
if not project_matches and not log_matches:
|
|
142
|
+
print(f"No results found for '{query}'.")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
if project_matches:
|
|
146
|
+
print(f"=== Projects matching '{query}' ===")
|
|
147
|
+
for m in project_matches:
|
|
148
|
+
buckets = ", ".join(m["matched_buckets"])
|
|
149
|
+
print(f"- {m['project']} (found in: {buckets})")
|
|
150
|
+
|
|
151
|
+
if log_matches:
|
|
152
|
+
print(f"\n=== History matching '{query}' ===")
|
|
153
|
+
for m in log_matches:
|
|
154
|
+
print(f"- [{m['project']}] [{m['tool_name']} @ {m['timestamp'][:16]}] {m['summary'][:100]}")
|
|
155
|
+
|
|
156
|
+
print(f"\nTotal: {len(project_matches)} projects, {len(log_matches)} history entries")
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
cli()
|