vs-agent 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vs_agent-0.1.1/PKG-INFO +692 -0
- vs_agent-0.1.1/README.md +662 -0
- vs_agent-0.1.1/pyproject.toml +54 -0
- vs_agent-0.1.1/setup.cfg +4 -0
- vs_agent-0.1.1/vs_agent/__init__.py +5 -0
- vs_agent-0.1.1/vs_agent/auth/__init__.py +0 -0
- vs_agent-0.1.1/vs_agent/auth/vs_action_security.py +16 -0
- vs_agent-0.1.1/vs_agent/client/__init__.py +0 -0
- vs_agent-0.1.1/vs_agent/client/vs_peer_agent_client.py +72 -0
- vs_agent-0.1.1/vs_agent/decorator/__init__.py +0 -0
- vs_agent-0.1.1/vs_agent/decorator/vs_action_decorator.py +23 -0
- vs_agent-0.1.1/vs_agent/discovery/__init__.py +0 -0
- vs_agent-0.1.1/vs_agent/discovery/vs_peer_agent_registry.py +73 -0
- vs_agent-0.1.1/vs_agent/discovery/vs_peer_agent_scheduler.py +34 -0
- vs_agent-0.1.1/vs_agent/discovery/vs_peer_capability_loader.py +81 -0
- vs_agent-0.1.1/vs_agent/registry/__init__.py +0 -0
- vs_agent-0.1.1/vs_agent/registry/vs_action_registry.py +51 -0
- vs_agent-0.1.1/vs_agent/schema/__init__.py +0 -0
- vs_agent-0.1.1/vs_agent/schema/vs_action_schema.py +21 -0
- vs_agent-0.1.1/vs_agent/server/__init__.py +0 -0
- vs_agent-0.1.1/vs_agent/server/vs_agent_server.py +182 -0
- vs_agent-0.1.1/vs_agent.egg-info/PKG-INFO +692 -0
- vs_agent-0.1.1/vs_agent.egg-info/SOURCES.txt +24 -0
- vs_agent-0.1.1/vs_agent.egg-info/dependency_links.txt +1 -0
- vs_agent-0.1.1/vs_agent.egg-info/requires.txt +14 -0
- vs_agent-0.1.1/vs_agent.egg-info/top_level.txt +1 -0
vs_agent-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vs-agent
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Full-stack agent framework for Viveka Sutra — HTTP + MCP server in one
|
|
5
|
+
Project-URL: Homepage, https://vivekasutra.com/
|
|
6
|
+
Project-URL: Source, https://github.com/vivekasutra/viveka-mula
|
|
7
|
+
Keywords: agent,mcp,fastapi,http,viveka,vs
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: Other/Proprietary License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Framework :: AsyncIO
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: vs-common
|
|
20
|
+
Requires-Dist: vs-server
|
|
21
|
+
Provides-Extra: mcp
|
|
22
|
+
Requires-Dist: vs-mcp-agent; extra == "mcp"
|
|
23
|
+
Provides-Extra: security
|
|
24
|
+
Requires-Dist: vs-security; extra == "security"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
30
|
+
|
|
31
|
+
# vs-agent
|
|
32
|
+
|
|
33
|
+
Full-stack agent framework for Viveka Sutra — build agents that serve over HTTP, MCP, or both, from a single codebase.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Overview
|
|
38
|
+
|
|
39
|
+
`vs-agent` combines `vs-server` (HTTP/WebSocket/SSE) and `vs-mcp-agent` (MCP) into a single `VsAgentServer`. It lets you expose a capability once with `@action` and have it available simultaneously as an HTTP endpoint and an MCP tool — with the same auth, guards, and business logic.
|
|
40
|
+
|
|
41
|
+
The library is fully server-agnostic. `VsAgentServer` does not hardcode FastAPI or FastMCP — it uses `VsServerFactory` and `VsMcpServerFactory` to resolve the correct server implementation at startup. Swapping or adding a new server type requires no changes to application code.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## The Problem It Solves
|
|
46
|
+
|
|
47
|
+
An agent needs to be callable by both humans (via HTTP) and AI clients (via MCP). Without `vs-agent`, you register the same function twice, apply guards twice, and maintain two sets of route definitions.
|
|
48
|
+
|
|
49
|
+
### Without vs-agent
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# HTTP route
|
|
53
|
+
@router.post("/v1/docs/search")
|
|
54
|
+
async def search_docs_http(query: str, auth=Depends(require_auth)):
|
|
55
|
+
return await _search(query)
|
|
56
|
+
|
|
57
|
+
# MCP tool — separate registration, separate guard wiring
|
|
58
|
+
@mcp.tool(name="search_docs")
|
|
59
|
+
async def search_docs_mcp(query: str) -> dict:
|
|
60
|
+
await require_auth()
|
|
61
|
+
return await _search(query)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### With vs-agent
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
@action(
|
|
68
|
+
name="search_docs",
|
|
69
|
+
description="Search VS library documentation",
|
|
70
|
+
path="/v1/docs/search",
|
|
71
|
+
guards=[VsActionSecurity(roles=["user"])],
|
|
72
|
+
)
|
|
73
|
+
async def search_docs(query: str) -> VsToolResponse:
|
|
74
|
+
return VsToolResponse(status="success", result=await _search(query))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
One function, one guard, available on both protocols.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install vs-agent
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
With MCP support:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install vs-agent[mcp]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With auth guard support:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install vs-agent[mcp,security]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Dependencies
|
|
102
|
+
|
|
103
|
+
| Library | Required | Purpose |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `vs-common` | Yes | Config, logging |
|
|
106
|
+
| `vs-server` | Yes | HTTP/WebSocket/SSE server |
|
|
107
|
+
| `vs-mcp-agent` | No — install with `[mcp]` extra | MCP server |
|
|
108
|
+
| `vs-security` | No — install with `[security]` extra | JWT auth and `VsActionSecurity` guard |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
|
|
114
|
+
`VsAgentServer` reads HTTP config via `vs-server` and MCP config via `vs-mcp-agent`. Both use the same `config.ini` file — separated by section.
|
|
115
|
+
|
|
116
|
+
**HTTP section (from `vs-server`):**
|
|
117
|
+
|
|
118
|
+
| Key | Default | Description |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `server.name` | `vs-agent` | Server name shown in logs and `/` response |
|
|
121
|
+
| `server.version` | `0.1.0` | Version shown in logs and `/` response |
|
|
122
|
+
| `server.host` | `0.0.0.0` | Host to bind |
|
|
123
|
+
| `server.port` | `8000` | HTTP port |
|
|
124
|
+
| `server.reload` | `false` | Enable hot reload (dev only) |
|
|
125
|
+
| `server.workers` | `1` | Number of worker processes |
|
|
126
|
+
| `server.cors_origins` | `*` | Comma-separated allowed CORS origins |
|
|
127
|
+
| `server.ssl_certfile` | — | Path to TLS certificate file |
|
|
128
|
+
| `server.ssl_keyfile` | — | Path to TLS private key file |
|
|
129
|
+
|
|
130
|
+
**MCP section (from `vs-mcp-agent`):**
|
|
131
|
+
|
|
132
|
+
| Key | Default | Description |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `agent.name` | `vs-agent` | MCP server name |
|
|
135
|
+
| `agent.version` | `0.1.0` | MCP server version |
|
|
136
|
+
| `agent.transport` | `streamable-http` | Transport: `stdio`, `sse`, `streamable-http` |
|
|
137
|
+
| `agent.host` | `0.0.0.0` | MCP host |
|
|
138
|
+
| `agent.port` | `8080` | MCP port |
|
|
139
|
+
| `agent.auth_enabled` | `false` | Enable JWT auth middleware on MCP |
|
|
140
|
+
|
|
141
|
+
**`config.ini` example:**
|
|
142
|
+
|
|
143
|
+
```ini
|
|
144
|
+
[server]
|
|
145
|
+
name = my-agent
|
|
146
|
+
version = 1.0.0
|
|
147
|
+
host = 0.0.0.0
|
|
148
|
+
port = 8000
|
|
149
|
+
|
|
150
|
+
[agent]
|
|
151
|
+
name = my-agent
|
|
152
|
+
version = 1.0.0
|
|
153
|
+
transport = streamable-http
|
|
154
|
+
host = 0.0.0.0
|
|
155
|
+
port = 8080
|
|
156
|
+
auth_enabled = true
|
|
157
|
+
|
|
158
|
+
[auth]
|
|
159
|
+
secret_key = your-secret-key
|
|
160
|
+
algorithm = HS256
|
|
161
|
+
|
|
162
|
+
[logging]
|
|
163
|
+
level = INFO
|
|
164
|
+
file_path = ./logs/agent.log
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Quick Start
|
|
170
|
+
|
|
171
|
+
### HTTP + MCP (most common)
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from vs_common.config.vs_ini_config import VsIniConfig
|
|
175
|
+
from vs_common.log.vs_log_manager import VsLogManager
|
|
176
|
+
from vs_common.schema.vs_log_config import VsLogConfig
|
|
177
|
+
from vs_server.server.vs_fast_api_server import VsFastApiServer # noqa — auto-registers "fastapi"
|
|
178
|
+
from vs_mcp_agent.server.vs_fast_mcp_server import VsFastMcpServer # noqa — auto-registers "fastmcp"
|
|
179
|
+
from vs_agent.server.vs_agent_server import VsAgentServer
|
|
180
|
+
|
|
181
|
+
import my_agent.actions # noqa — registers @action functions
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def main():
|
|
185
|
+
config = VsIniConfig("config.ini")
|
|
186
|
+
VsLogManager.init(VsLogConfig(level=config.get("logging.level", default="INFO")))
|
|
187
|
+
|
|
188
|
+
server = VsAgentServer(config, http="fastapi", mcp="fastmcp")
|
|
189
|
+
server.add_actions()
|
|
190
|
+
server.run()
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
main()
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### HTTP only
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
server = VsAgentServer(config, http="fastapi")
|
|
201
|
+
server.add_actions()
|
|
202
|
+
server.run()
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### MCP only
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from vs_mcp_agent.server.vs_fast_mcp_server import VsFastMcpServer # noqa — auto-registers "fastmcp"
|
|
209
|
+
|
|
210
|
+
server = VsAgentServer(config, mcp="fastmcp")
|
|
211
|
+
server.run()
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## How It All Fits Together
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
Application Startup
|
|
220
|
+
└── import VsFastApiServer # auto-registers "fastapi" into VsServerFactory
|
|
221
|
+
└── import VsFastMcpServer # auto-registers "fastmcp" into VsMcpServerFactory
|
|
222
|
+
└── import my_agent.actions # @action decorators self-register into VsActionRegistry + VsToolRegistry
|
|
223
|
+
|
|
224
|
+
VsAgentServer(config, http="fastapi", mcp="fastmcp")
|
|
225
|
+
├── VsServerFactory.get("fastapi", config) → VsFastApiServer
|
|
226
|
+
└── VsMcpServerFactory.get("fastmcp", config) → VsFastMcpServer
|
|
227
|
+
|
|
228
|
+
server.add_actions()
|
|
229
|
+
├── reads VsActionRegistry → wires HTTP routes + /capabilities
|
|
230
|
+
└── VsFastMcpServer already has tools from VsToolRegistry (wired at run())
|
|
231
|
+
|
|
232
|
+
server.run()
|
|
233
|
+
├── MCP server starts on background thread (port 8080)
|
|
234
|
+
└── HTTP server starts on main thread (port 8000)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## VsAgentServer
|
|
240
|
+
|
|
241
|
+
`VsAgentServer` is the central coordinator. It creates and manages HTTP and MCP server instances via their respective factories.
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from vs_agent.server.vs_agent_server import VsAgentServer
|
|
245
|
+
|
|
246
|
+
server = VsAgentServer(config, http="fastapi", mcp="fastmcp")
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Constructor parameters:**
|
|
250
|
+
|
|
251
|
+
| Parameter | Type | Default | Description |
|
|
252
|
+
|---|---|---|---|
|
|
253
|
+
| `config` | `VsBaseConfig` | — | Application config |
|
|
254
|
+
| `http` | `Optional[str]` | `"fastapi"` | HTTP server key. Pass `None` for MCP-only mode. |
|
|
255
|
+
| `mcp` | `Optional[str]` | `None` | MCP server key. Pass `"fastmcp"` to enable MCP. |
|
|
256
|
+
|
|
257
|
+
At least one of `http` or `mcp` must be specified — both `None` raises `ValueError`.
|
|
258
|
+
|
|
259
|
+
**Methods:**
|
|
260
|
+
|
|
261
|
+
| Method | Description |
|
|
262
|
+
|---|---|
|
|
263
|
+
| `add_controller(controller)` | Register an HTTP `@controller` class. Delegates to the HTTP server. |
|
|
264
|
+
| `add_router(router)` | Register a raw router. Delegates to the HTTP server. |
|
|
265
|
+
| `add_websocket(handler)` | Register a `@websocket` handler. Delegates to the HTTP server. |
|
|
266
|
+
| `add_sse(handler)` | Register an `@sse` handler. Delegates to the HTTP server. |
|
|
267
|
+
| `add_actions()` | Wire all `@action` functions to HTTP routes and expose `/capabilities`. |
|
|
268
|
+
| `get_app()` | Return the underlying ASGI app (e.g. FastAPI instance). |
|
|
269
|
+
| `run()` | Start the server(s). MCP runs on a background daemon thread; HTTP runs on the main thread. |
|
|
270
|
+
|
|
271
|
+
**`mcp` property:**
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
server.mcp # returns the underlying FastMCP instance for advanced configuration
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Raises `RuntimeError` if no MCP server is configured.
|
|
278
|
+
|
|
279
|
+
**All `add_*` methods return `self` for chaining:**
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
server = (
|
|
283
|
+
VsAgentServer(config, http="fastapi", mcp="fastmcp")
|
|
284
|
+
.add_controller(HealthController())
|
|
285
|
+
.add_actions()
|
|
286
|
+
)
|
|
287
|
+
server.run()
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## @action
|
|
293
|
+
|
|
294
|
+
Registers a function simultaneously as:
|
|
295
|
+
- An MCP tool — in `VsToolRegistry`, picked up by `VsFastMcpServer` when `run()` is called
|
|
296
|
+
- An HTTP endpoint — in `VsActionRegistry`, wired by `server.add_actions()`
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from vs_agent.decorator.vs_action_decorator import action
|
|
300
|
+
from vs_mcp_agent.schema.vs_tool_response import VsToolResponse
|
|
301
|
+
|
|
302
|
+
@action(
|
|
303
|
+
name="search_docs",
|
|
304
|
+
description="Search VS library documentation",
|
|
305
|
+
path="/v1/docs/search",
|
|
306
|
+
method="POST",
|
|
307
|
+
guards=[VsActionSecurity(roles=["user"])],
|
|
308
|
+
)
|
|
309
|
+
async def search_docs(query: str) -> VsToolResponse:
|
|
310
|
+
results = await _do_search(query)
|
|
311
|
+
return VsToolResponse(status="success", result=results, summary="Search complete")
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Parameters:**
|
|
315
|
+
|
|
316
|
+
| Parameter | Type | Required | Default | Description |
|
|
317
|
+
|---|---|---|---|---|
|
|
318
|
+
| `name` | `str` | Yes | — | MCP tool name and action identifier |
|
|
319
|
+
| `description` | `str` | Yes | — | Shown in MCP tool list and `/capabilities` |
|
|
320
|
+
| `path` | `str` | Yes | — | HTTP endpoint path |
|
|
321
|
+
| `method` | `str` | No | `"POST"` | HTTP method (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) |
|
|
322
|
+
| `intents` | `List[Intent]` | No | `[]` | Semantic intents for agent routing |
|
|
323
|
+
| `input_schema` | `Dict[str, Any]` | No | `None` | JSON schema for the action input |
|
|
324
|
+
| `guards` | `List[Callable]` | No | `[]` | Guards applied on both HTTP and MCP |
|
|
325
|
+
|
|
326
|
+
**Import order matters.** `@action` self-registers at import time into both `VsActionRegistry` and `VsToolRegistry`. Import your action modules before calling `server.add_actions()` or `server.run()`.
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
import my_agent.actions.search # noqa — triggers @action registration
|
|
330
|
+
import my_agent.actions.summary # noqa
|
|
331
|
+
|
|
332
|
+
server.add_actions()
|
|
333
|
+
server.run()
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Authorization with VsActionSecurity
|
|
339
|
+
|
|
340
|
+
`VsActionSecurity` is a unified guard that works on both HTTP and MCP. It reads the auth context set by `VsSecurityFactory` (HTTP) or `VsMcpAuthMiddleware` (MCP).
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from vs_agent.auth.vs_action_security import VsActionSecurity
|
|
344
|
+
|
|
345
|
+
@action(
|
|
346
|
+
name="search_docs",
|
|
347
|
+
description="Search documentation",
|
|
348
|
+
path="/v1/docs/search",
|
|
349
|
+
guards=[VsActionSecurity(roles=["user"])],
|
|
350
|
+
)
|
|
351
|
+
async def search_docs(query: str) -> VsToolResponse:
|
|
352
|
+
...
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**No roles (auth only):**
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
guards=[VsActionSecurity()] # rejects unauthenticated callers, any role allowed
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Multiple roles (any match):**
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
guards=[VsActionSecurity(roles=["admin", "editor"])] # passes if caller has admin OR editor
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Multiple guards (all must pass, in order):**
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
guards=[VsActionSecurity(roles=["admin"]), require_verified_account]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**How guards run per protocol:**
|
|
374
|
+
|
|
375
|
+
| Protocol | Auth context source | Guard execution |
|
|
376
|
+
|---|---|---|
|
|
377
|
+
| HTTP | `VsSecurityFactory.get()` sets context via FastAPI `Depends` | Guards called in order after auth context is set |
|
|
378
|
+
| MCP | `VsMcpAuthMiddleware` sets context before tool dispatch | Guards called in order before tool function executes |
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Lifecycle Hooks
|
|
383
|
+
|
|
384
|
+
Use lifecycle hooks to run code at server startup and shutdown — creating DB tables, warming caches, closing connections, etc.
|
|
385
|
+
|
|
386
|
+
### HTTP lifecycle (`vs-server`)
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
from vs_server.lifecycle.vs_lifecycle import startup, shutdown
|
|
390
|
+
|
|
391
|
+
@startup
|
|
392
|
+
async def warm_cache():
|
|
393
|
+
await VsCacheManager.set("ready", True)
|
|
394
|
+
|
|
395
|
+
@shutdown
|
|
396
|
+
async def flush_cache():
|
|
397
|
+
await VsCacheManager.delete("ready")
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Register hook modules with `server_registry`:
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
from vs_server.decorator.vs_server_registry import server_registry
|
|
404
|
+
|
|
405
|
+
@server_registry(hooks="my_agent.lifecycle")
|
|
406
|
+
def main():
|
|
407
|
+
...
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### MCP lifecycle (`vs-mcp-agent`)
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
from vs_mcp_agent.lifecycle.vs_mcp_lifecycle import mcp_startup, mcp_shutdown
|
|
414
|
+
|
|
415
|
+
@mcp_startup
|
|
416
|
+
async def init_mcp_resources():
|
|
417
|
+
await load_tool_index()
|
|
418
|
+
|
|
419
|
+
@mcp_shutdown
|
|
420
|
+
async def cleanup_mcp_resources():
|
|
421
|
+
await close_tool_connections()
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Register hook modules with `mcp_server_registry`:
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
from vs_mcp_agent.decorator.vs_mcp_server_registry import mcp_server_registry
|
|
428
|
+
|
|
429
|
+
@mcp_server_registry(hooks="my_agent.mcp_lifecycle")
|
|
430
|
+
def main():
|
|
431
|
+
...
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
When both are used, HTTP and MCP hooks run independently — HTTP hooks fire when the HTTP server starts/stops, MCP hooks fire when the MCP server starts/stops.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## HTTP-only Capabilities
|
|
439
|
+
|
|
440
|
+
For capabilities that should only be available over HTTP, use `@controller` from `vs-server` directly and register via `server.add_controller()`:
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
from vs_server.decorator.vs_controller_decorator import controller, get, post
|
|
444
|
+
|
|
445
|
+
@controller("/v1/internal")
|
|
446
|
+
class InternalController:
|
|
447
|
+
|
|
448
|
+
@get("/status")
|
|
449
|
+
async def status(self):
|
|
450
|
+
return {"status": "ok"}
|
|
451
|
+
|
|
452
|
+
server.add_controller(InternalController())
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## MCP-only Capabilities
|
|
458
|
+
|
|
459
|
+
For capabilities that should only be available over MCP, use `@tool` from `vs-mcp-agent` directly:
|
|
460
|
+
|
|
461
|
+
```python
|
|
462
|
+
from vs_mcp_agent.decorator.tool import tool
|
|
463
|
+
from vs_mcp_agent.schema.vs_tool_response import VsToolResponse
|
|
464
|
+
|
|
465
|
+
@tool(name="internal_tool", description="Internal MCP tool only")
|
|
466
|
+
async def internal_tool(query: str) -> VsToolResponse:
|
|
467
|
+
...
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
These are picked up automatically by `VsFastMcpServer` at `run()` — no extra registration needed.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## /capabilities Endpoint
|
|
475
|
+
|
|
476
|
+
`server.add_actions()` automatically registers a `GET /capabilities` endpoint on the HTTP server. It returns all registered actions with their metadata — used by orchestrators to discover what the agent can do.
|
|
477
|
+
|
|
478
|
+
**Response:**
|
|
479
|
+
|
|
480
|
+
```json
|
|
481
|
+
{
|
|
482
|
+
"actions": [
|
|
483
|
+
{
|
|
484
|
+
"name": "search_docs",
|
|
485
|
+
"description": "Search VS library documentation",
|
|
486
|
+
"path": "/v1/docs/search",
|
|
487
|
+
"method": "POST",
|
|
488
|
+
"intents": [],
|
|
489
|
+
"input_schema": null
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Intents
|
|
498
|
+
|
|
499
|
+
`Intent` gives each action semantic labels that orchestrators use for routing — matching a user request to the right action without exact string matching.
|
|
500
|
+
|
|
501
|
+
```python
|
|
502
|
+
from vs_agent.schema.vs_action_schema import Intent
|
|
503
|
+
|
|
504
|
+
@action(
|
|
505
|
+
name="search_docs",
|
|
506
|
+
description="Search VS library documentation",
|
|
507
|
+
path="/v1/docs/search",
|
|
508
|
+
intents=[
|
|
509
|
+
Intent(
|
|
510
|
+
name="search",
|
|
511
|
+
description="Find documentation matching a query",
|
|
512
|
+
examples=["how do I configure logging", "what does VsLogManager do"],
|
|
513
|
+
)
|
|
514
|
+
],
|
|
515
|
+
)
|
|
516
|
+
async def search_docs(query: str) -> VsToolResponse:
|
|
517
|
+
...
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Intents are returned in `/capabilities` and are available as metadata on `VsActionRegistry` entries.
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Error Handling
|
|
525
|
+
|
|
526
|
+
Exceptions from `@action` functions propagate through both protocols:
|
|
527
|
+
- On HTTP, `vs-server`'s exception handlers convert them to HTTP responses.
|
|
528
|
+
- On MCP, `VsFastMcpServer` returns an error result to the MCP client.
|
|
529
|
+
|
|
530
|
+
Use exceptions from `vs-server` for standard HTTP error semantics — they are handled automatically:
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
from vs_server.schema.exceptions import NotFoundException, ServiceUnavailableException
|
|
534
|
+
|
|
535
|
+
@action(name="get_doc", description="Get a document", path="/v1/docs/{doc_id}")
|
|
536
|
+
async def get_doc(doc_id: str) -> VsToolResponse:
|
|
537
|
+
doc = await repo.find(doc_id)
|
|
538
|
+
if doc is None:
|
|
539
|
+
raise NotFoundException(f"Document '{doc_id}' not found")
|
|
540
|
+
return VsToolResponse(status="success", result=doc)
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## Class Reference
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
### VsAgentServer
|
|
550
|
+
|
|
551
|
+
Coordinates HTTP and MCP servers. Uses `VsServerFactory` and `VsMcpServerFactory` to resolve server implementations. Supports HTTP-only, MCP-only, or HTTP + MCP modes.
|
|
552
|
+
|
|
553
|
+
**Constructor:**
|
|
554
|
+
|
|
555
|
+
| Parameter | Type | Default | Description |
|
|
556
|
+
|---|---|---|---|
|
|
557
|
+
| `config` | `VsBaseConfig` | — | Application config |
|
|
558
|
+
| `http` | `Optional[str]` | `"fastapi"` | HTTP server key registered in `VsServerFactory`. Pass `None` for MCP-only. |
|
|
559
|
+
| `mcp` | `Optional[str]` | `None` | MCP server key registered in `VsMcpServerFactory`. Pass `"fastmcp"` to enable MCP. |
|
|
560
|
+
|
|
561
|
+
**Methods:**
|
|
562
|
+
|
|
563
|
+
| Method | Signature | Description |
|
|
564
|
+
|---|---|---|
|
|
565
|
+
| `add_controller` | `add_controller(controller) -> VsAgentServer` | Register an HTTP controller. Requires `http` mode. |
|
|
566
|
+
| `add_router` | `add_router(router) -> VsAgentServer` | Register a raw router. Requires `http` mode. |
|
|
567
|
+
| `add_websocket` | `add_websocket(handler) -> VsAgentServer` | Register a WebSocket handler. Requires `http` mode. |
|
|
568
|
+
| `add_sse` | `add_sse(handler) -> VsAgentServer` | Register an SSE handler. Requires `http` mode. |
|
|
569
|
+
| `add_actions` | `add_actions() -> VsAgentServer` | Wire all `@action` functions to HTTP routes and `/capabilities`. |
|
|
570
|
+
| `get_app` | `get_app() -> Any` | Return the underlying ASGI app. Requires `http` mode. |
|
|
571
|
+
| `run` | `run() -> None` | Start all servers. MCP on daemon thread, HTTP on main thread. |
|
|
572
|
+
|
|
573
|
+
**Property:**
|
|
574
|
+
|
|
575
|
+
| Property | Type | Description |
|
|
576
|
+
|---|---|---|
|
|
577
|
+
| `mcp` | `Any` | The underlying FastMCP instance. Raises `RuntimeError` if MCP not configured. |
|
|
578
|
+
|
|
579
|
+
**Notes:**
|
|
580
|
+
- All `add_*` methods call `_require_http()` internally and raise `RuntimeError` if `http=None`.
|
|
581
|
+
- In HTTP + MCP mode, `run()` starts MCP on a background daemon thread then blocks on the HTTP server.
|
|
582
|
+
- Import `VsFastApiServer` before constructing `VsAgentServer` to ensure `"fastapi"` is registered.
|
|
583
|
+
- Import `VsFastMcpServer` before constructing `VsAgentServer` to ensure `"fastmcp"` is registered.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
### `@action`
|
|
588
|
+
|
|
589
|
+
Decorator. Registers a function as both an MCP tool (via `VsToolRegistry`) and an HTTP action (via `VsActionRegistry`). Self-registers at import time.
|
|
590
|
+
|
|
591
|
+
**Parameters:**
|
|
592
|
+
|
|
593
|
+
| Parameter | Type | Required | Default | Description |
|
|
594
|
+
|---|---|---|---|---|
|
|
595
|
+
| `name` | `str` | Yes | — | MCP tool name and action key |
|
|
596
|
+
| `description` | `str` | Yes | — | Shown in MCP tool list and `/capabilities` |
|
|
597
|
+
| `path` | `str` | Yes | — | HTTP endpoint path |
|
|
598
|
+
| `method` | `str` | No | `"POST"` | HTTP method |
|
|
599
|
+
| `intents` | `List[Intent]` | No | `[]` | Semantic intents for orchestrator routing |
|
|
600
|
+
| `input_schema` | `Dict[str, Any]` | No | `None` | JSON schema for the action input |
|
|
601
|
+
| `guards` | `List[Callable]` | No | `[]` | Applied on both HTTP and MCP, in order |
|
|
602
|
+
|
|
603
|
+
**Notes:**
|
|
604
|
+
- Each `name` must be unique across all `@action` registrations. Duplicate names raise `ValueError`.
|
|
605
|
+
- `method` is HTTP-only — MCP tools have no HTTP method concept.
|
|
606
|
+
- `guards` must be async callables.
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
### VsActionSecurity
|
|
611
|
+
|
|
612
|
+
Guard class for unified HTTP + MCP authorization. Reads from the auth context set by whichever protocol is active.
|
|
613
|
+
|
|
614
|
+
**Constructor:**
|
|
615
|
+
|
|
616
|
+
| Parameter | Type | Default | Description |
|
|
617
|
+
|---|---|---|---|
|
|
618
|
+
| `roles` | `Optional[List[str]]` | `[]` | Required roles. Caller must have at least one. Empty list = any authenticated caller. |
|
|
619
|
+
|
|
620
|
+
**Behaviour:**
|
|
621
|
+
|
|
622
|
+
| Condition | Result |
|
|
623
|
+
|---|---|
|
|
624
|
+
| No auth context | Raises `PermissionError("Unauthenticated request")` |
|
|
625
|
+
| Auth context present, no roles required | Passes |
|
|
626
|
+
| Auth context present, caller has required role | Passes |
|
|
627
|
+
| Auth context present, caller lacks required role | Raises `PermissionError` |
|
|
628
|
+
|
|
629
|
+
**Notes:**
|
|
630
|
+
- On HTTP, auth context is set by `VsSecurityFactory.get()` (from `vs-security`).
|
|
631
|
+
- On MCP, auth context is set by `VsMcpAuthMiddleware` (from `vs-mcp-agent`).
|
|
632
|
+
- `VsActionSecurity` reads from the same context variable regardless of protocol.
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
### VsActionRegistry
|
|
637
|
+
|
|
638
|
+
Class-level registry of all `@action`-registered functions. Thread-safe.
|
|
639
|
+
|
|
640
|
+
**Methods:**
|
|
641
|
+
|
|
642
|
+
| Method | Signature | Description |
|
|
643
|
+
|---|---|---|
|
|
644
|
+
| `register` | `register(name, description, path, method, intents, input_schema, guards, fn) -> None` | Register an action. Raises `ValueError` if name is already registered. |
|
|
645
|
+
| `get_all` | `get_all() -> Dict[str, _ActionEntry]` | Returns a snapshot of all registered actions. |
|
|
646
|
+
|
|
647
|
+
**Notes:**
|
|
648
|
+
- `@action` calls `VsActionRegistry.register()` and `VsToolRegistry.register_fn()` at decoration time.
|
|
649
|
+
- `server.add_actions()` reads from `VsActionRegistry.get_all()` to wire HTTP routes.
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
### Intent
|
|
654
|
+
|
|
655
|
+
Pydantic model. Semantic label for an action — used by orchestrators for routing.
|
|
656
|
+
|
|
657
|
+
**Fields:**
|
|
658
|
+
|
|
659
|
+
| Field | Type | Required | Description |
|
|
660
|
+
|---|---|---|---|
|
|
661
|
+
| `name` | `str` | Yes | Short intent identifier |
|
|
662
|
+
| `description` | `str` | Yes | What this intent means |
|
|
663
|
+
| `examples` | `Optional[List[str]]` | No | Example user phrases that trigger this intent |
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
### ActionCapability
|
|
668
|
+
|
|
669
|
+
Pydantic model. Metadata for a single action as returned by `/capabilities`.
|
|
670
|
+
|
|
671
|
+
**Fields:**
|
|
672
|
+
|
|
673
|
+
| Field | Type | Description |
|
|
674
|
+
|---|---|---|
|
|
675
|
+
| `name` | `str` | Action name |
|
|
676
|
+
| `description` | `str` | Action description |
|
|
677
|
+
| `path` | `str` | HTTP endpoint path |
|
|
678
|
+
| `method` | `str` | HTTP method |
|
|
679
|
+
| `intents` | `List[Intent]` | Semantic intents |
|
|
680
|
+
| `input_schema` | `Optional[Dict[str, Any]]` | JSON schema for input |
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
### CapabilitiesResponse
|
|
685
|
+
|
|
686
|
+
Pydantic model. Response from `GET /capabilities`.
|
|
687
|
+
|
|
688
|
+
**Fields:**
|
|
689
|
+
|
|
690
|
+
| Field | Type | Description |
|
|
691
|
+
|---|---|---|
|
|
692
|
+
| `actions` | `List[ActionCapability]` | All registered actions |
|