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.
@@ -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 |