vs-mcp-agent 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.
Files changed (43) hide show
  1. vs_mcp_agent-0.1.0/PKG-INFO +956 -0
  2. vs_mcp_agent-0.1.0/README.md +925 -0
  3. vs_mcp_agent-0.1.0/pyproject.toml +55 -0
  4. vs_mcp_agent-0.1.0/setup.cfg +4 -0
  5. vs_mcp_agent-0.1.0/vs_mcp_agent/__init__.py +49 -0
  6. vs_mcp_agent-0.1.0/vs_mcp_agent/auth/__init__.py +0 -0
  7. vs_mcp_agent-0.1.0/vs_mcp_agent/auth/vs_mcp_auth.py +46 -0
  8. vs_mcp_agent-0.1.0/vs_mcp_agent/auth/vs_mcp_security.py +16 -0
  9. vs_mcp_agent-0.1.0/vs_mcp_agent/base/__init__.py +0 -0
  10. vs_mcp_agent-0.1.0/vs_mcp_agent/base/vs_base_prompt.py +14 -0
  11. vs_mcp_agent-0.1.0/vs_mcp_agent/base/vs_base_resource.py +14 -0
  12. vs_mcp_agent-0.1.0/vs_mcp_agent/base/vs_base_tool.py +14 -0
  13. vs_mcp_agent-0.1.0/vs_mcp_agent/config/__init__.py +0 -0
  14. vs_mcp_agent-0.1.0/vs_mcp_agent/config/vs_agent_mcp_config.py +21 -0
  15. vs_mcp_agent-0.1.0/vs_mcp_agent/decorator/__init__.py +0 -0
  16. vs_mcp_agent-0.1.0/vs_mcp_agent/decorator/prompt.py +11 -0
  17. vs_mcp_agent-0.1.0/vs_mcp_agent/decorator/resource.py +11 -0
  18. vs_mcp_agent-0.1.0/vs_mcp_agent/decorator/tool.py +14 -0
  19. vs_mcp_agent-0.1.0/vs_mcp_agent/decorator/vs_mcp_server_decorator.py +11 -0
  20. vs_mcp_agent-0.1.0/vs_mcp_agent/decorator/vs_mcp_server_registry.py +40 -0
  21. vs_mcp_agent-0.1.0/vs_mcp_agent/factory/__init__.py +0 -0
  22. vs_mcp_agent-0.1.0/vs_mcp_agent/factory/vs_mcp_server_factory.py +23 -0
  23. vs_mcp_agent-0.1.0/vs_mcp_agent/lifecycle/__init__.py +0 -0
  24. vs_mcp_agent-0.1.0/vs_mcp_agent/lifecycle/vs_mcp_lifecycle.py +14 -0
  25. vs_mcp_agent-0.1.0/vs_mcp_agent/registry/__init__.py +0 -0
  26. vs_mcp_agent-0.1.0/vs_mcp_agent/registry/vs_prompt_registry.py +28 -0
  27. vs_mcp_agent-0.1.0/vs_mcp_agent/registry/vs_resource_registry.py +29 -0
  28. vs_mcp_agent-0.1.0/vs_mcp_agent/registry/vs_tool_registry.py +38 -0
  29. vs_mcp_agent-0.1.0/vs_mcp_agent/schema/__init__.py +0 -0
  30. vs_mcp_agent-0.1.0/vs_mcp_agent/schema/vs_prompt_response.py +11 -0
  31. vs_mcp_agent-0.1.0/vs_mcp_agent/schema/vs_resource_response.py +12 -0
  32. vs_mcp_agent-0.1.0/vs_mcp_agent/schema/vs_tool_response.py +63 -0
  33. vs_mcp_agent-0.1.0/vs_mcp_agent/server/__init__.py +0 -0
  34. vs_mcp_agent-0.1.0/vs_mcp_agent/server/vs_fast_mcp_server.py +162 -0
  35. vs_mcp_agent-0.1.0/vs_mcp_agent/server/vs_mcp_server.py +34 -0
  36. vs_mcp_agent-0.1.0/vs_mcp_agent/tools/__init__.py +0 -0
  37. vs_mcp_agent-0.1.0/vs_mcp_agent/tools/vs_health_tool.py +33 -0
  38. vs_mcp_agent-0.1.0/vs_mcp_agent/tools/vs_ping_tool.py +18 -0
  39. vs_mcp_agent-0.1.0/vs_mcp_agent.egg-info/PKG-INFO +956 -0
  40. vs_mcp_agent-0.1.0/vs_mcp_agent.egg-info/SOURCES.txt +41 -0
  41. vs_mcp_agent-0.1.0/vs_mcp_agent.egg-info/dependency_links.txt +1 -0
  42. vs_mcp_agent-0.1.0/vs_mcp_agent.egg-info/requires.txt +15 -0
  43. vs_mcp_agent-0.1.0/vs_mcp_agent.egg-info/top_level.txt +1 -0
@@ -0,0 +1,956 @@
1
+ Metadata-Version: 2.4
2
+ Name: vs-mcp-agent
3
+ Version: 0.1.0
4
+ Summary: MCP agent framework for Viveka Sutra — build FastMCP-powered agents with tools, resources, and prompts
5
+ Project-URL: Homepage, https://vivekasutra.com/
6
+ Project-URL: Source, https://github.com/vivekasutra/viveka-mula
7
+ Keywords: mcp,fastmcp,agent,tools,async,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: pydantic>=2.0
20
+ Requires-Dist: vs-common>=0.1.3
21
+ Provides-Extra: fastmcp
22
+ Requires-Dist: fastmcp>=2.0; extra == "fastmcp"
23
+ Requires-Dist: uvicorn>=0.29; extra == "fastmcp"
24
+ Provides-Extra: security
25
+ Requires-Dist: vs-security; extra == "security"
26
+ Provides-Extra: dev
27
+ Requires-Dist: build; extra == "dev"
28
+ Requires-Dist: twine; extra == "dev"
29
+ Requires-Dist: pytest>=8.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
31
+
32
+ # vs-mcp-agent
33
+
34
+ MCP server framework for Viveka Sutra — build agents that expose tools, resources, and prompts over the Model Context Protocol, with pluggable server implementations, lifecycle hooks, and unified auth.
35
+
36
+ ---
37
+
38
+ ## Overview
39
+
40
+ `vs-mcp-agent` provides everything needed to build an MCP server: base classes for tools, resources, and prompts; decorators that self-register them at import time; a pluggable server factory; and lifecycle hooks. `VsFastMcpServer` ships as the built-in implementation backed by FastMCP, and auto-registers as `"fastmcp"` when its module is imported.
41
+
42
+ The library is server-agnostic by design. `VsMcpServerFactory` maps string keys to `VsMcpServer` subclasses — swap or add implementations without changing application code.
43
+
44
+ ---
45
+
46
+ ## The Problem It Solves
47
+
48
+ Every MCP agent needs tools, auth, and a server. Without a shared framework, each agent re-implements the same wiring boilerplate.
49
+
50
+ ### Without vs-mcp-agent
51
+
52
+ ```python
53
+ from fastmcp import FastMCP
54
+
55
+ mcp = FastMCP(name="my-agent")
56
+
57
+ # manually wire each tool
58
+ @mcp.tool(name="search_docs")
59
+ async def search_docs(query: str) -> dict:
60
+ # auth? write it yourself
61
+ # error handling? write it yourself
62
+ results = await _do_search(query)
63
+ return {"results": results}
64
+
65
+ mcp.run(transport="streamable-http")
66
+ ```
67
+
68
+ ### With vs-mcp-agent
69
+
70
+ ```python
71
+ from vs_mcp_agent.decorator.tool import tool
72
+ from vs_mcp_agent.schema.vs_tool_response import VsToolResponse
73
+ from vs_mcp_agent.auth.vs_mcp_security import VsMcpSecurity
74
+
75
+ @tool(name="search_docs", description="Search VS library documentation",
76
+ guards=[VsMcpSecurity(roles=["user"])])
77
+ async def search_docs(query: str) -> VsToolResponse:
78
+ return VsToolResponse(status="success", result=await _do_search(query))
79
+ ```
80
+
81
+ Tools self-register, auth is declarative, and the server wires everything at startup.
82
+
83
+ ---
84
+
85
+ ## Installation
86
+
87
+ ```bash
88
+ pip install vs-mcp-agent
89
+ ```
90
+
91
+ With FastMCP server support:
92
+
93
+ ```bash
94
+ pip install vs-mcp-agent[fastmcp]
95
+ ```
96
+
97
+ With auth support:
98
+
99
+ ```bash
100
+ pip install vs-mcp-agent[fastmcp,security]
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Dependencies
106
+
107
+ | Library | Required | Purpose |
108
+ |---|---|---|
109
+ | `pydantic` | Yes | Tool, resource, and prompt input/output schemas |
110
+ | `vs-common` | Yes | Config, logging |
111
+ | `fastmcp` | No — install with `[fastmcp]` extra | FastMCP server implementation |
112
+ | `uvicorn` | No — install with `[fastmcp]` extra | ASGI server for HTTP transports |
113
+ | `vs-security` | No — install with `[security]` extra | JWT auth for `VsMcpAuthMiddleware` and `VsMcpSecurity` |
114
+
115
+ ---
116
+
117
+ ## Configuration
118
+
119
+ All MCP config keys live under the `[agent]` section of `config.ini`.
120
+
121
+ | Key | Default | Description |
122
+ |---|---|---|
123
+ | `agent.name` | `vs-agent` | MCP server name |
124
+ | `agent.version` | `0.1.0` | MCP server version |
125
+ | `agent.description` | `""` | MCP server description |
126
+ | `agent.transport` | `streamable-http` | Transport: `stdio`, `sse`, `streamable-http` |
127
+ | `agent.host` | `0.0.0.0` | Host to bind (HTTP transports only) |
128
+ | `agent.port` | `8080` | Port to bind (HTTP transports only) |
129
+ | `agent.auth_enabled` | `false` | Enable JWT auth middleware (HTTP transports only) |
130
+
131
+ **`config.ini` example:**
132
+
133
+ ```ini
134
+ [agent]
135
+ name = my-agent
136
+ version = 1.0.0
137
+ transport = streamable-http
138
+ host = 0.0.0.0
139
+ port = 8080
140
+ auth_enabled = true
141
+
142
+ [auth]
143
+ secret_key = your-secret-key
144
+ algorithm = HS256
145
+
146
+ [logging]
147
+ level = INFO
148
+ file_path = ./logs/agent.log
149
+ ```
150
+
151
+ **Transport options:**
152
+
153
+ | Transport | Description |
154
+ |---|---|
155
+ | `stdio` | Standard input/output — for local use by Claude Desktop and similar clients |
156
+ | `sse` | HTTP + Server-Sent Events |
157
+ | `streamable-http` | HTTP with streamable responses — recommended for network-accessible agents |
158
+
159
+ `host` and `port` are ignored for `stdio` transport.
160
+
161
+ ---
162
+
163
+ ## Quick Start
164
+
165
+ ```python
166
+ from vs_common.config.vs_ini_config import VsIniConfig
167
+ from vs_common.log.vs_log_manager import VsLogManager
168
+ from vs_common.schema.vs_log_config import VsLogConfig
169
+ from vs_mcp_agent.server.vs_fast_mcp_server import VsFastMcpServer # noqa — auto-registers "fastmcp"
170
+ from vs_mcp_agent.factory.vs_mcp_server_factory import VsMcpServerFactory
171
+ from vs_mcp_agent.decorator.vs_mcp_server_registry import mcp_server_registry
172
+
173
+ import my_agent.tools # noqa — registers @tool functions
174
+ import my_agent.resources # noqa — registers @resource classes
175
+ import my_agent.prompts # noqa — registers @prompt classes
176
+
177
+
178
+ @mcp_server_registry(hooks="my_agent.lifecycle")
179
+ def main():
180
+ config = VsIniConfig("config.ini")
181
+ VsLogManager.init(VsLogConfig(level=config.get("logging.level", default="INFO")))
182
+
183
+ server = VsMcpServerFactory.get("fastmcp", config)
184
+ server.run()
185
+
186
+
187
+ if __name__ == "__main__":
188
+ main()
189
+ ```
190
+
191
+ ---
192
+
193
+ ## How It All Fits Together
194
+
195
+ ```
196
+ Application Startup
197
+ └── import VsFastMcpServer # auto-registers "fastmcp" into VsMcpServerFactory
198
+ └── import my_agent.tools # @tool decorators self-register into VsToolRegistry
199
+ └── import my_agent.resources # @resource decorators self-register into VsResourceRegistry
200
+ └── import my_agent.prompts # @prompt decorators self-register into VsPromptRegistry
201
+
202
+ @mcp_server_registry(hooks="my_agent.lifecycle")
203
+ └── imports my_agent.lifecycle # @mcp_startup / @mcp_shutdown self-register into hook lists
204
+
205
+ VsMcpServerFactory.get("fastmcp", config)
206
+ └── constructs VsAgentMcpConfig(config)
207
+ └── instantiates VsFastMcpServer(mcp_config)
208
+
209
+ server.run()
210
+ ├── calls startup() → runs @mcp_startup hooks in order
211
+ ├── wires VsToolRegistry → FastMCP tools
212
+ ├── wires VsResourceRegistry → FastMCP resources
213
+ ├── wires VsPromptRegistry → FastMCP prompts
214
+ ├── wires built-in tools: ping, health
215
+ └── starts server on configured transport
216
+ ```
217
+
218
+ ---
219
+
220
+ ## VsMcpServer
221
+
222
+ Abstract base class for all MCP server implementations. Provides lifecycle hook execution and logging. All concrete servers must extend this.
223
+
224
+ ```python
225
+ from vs_mcp_agent.server.vs_mcp_server import VsMcpServer
226
+ from vs_mcp_agent.config.vs_agent_mcp_config import VsAgentMcpConfig
227
+ from vs_mcp_agent.decorator.vs_mcp_server_decorator import mcp_server
228
+ from vs_mcp_agent.factory.vs_mcp_server_factory import VsMcpServerFactory
229
+
230
+
231
+ @mcp_server("my-mcp")
232
+ class MyCustomMcpServer(VsMcpServer):
233
+
234
+ def __init__(self, config: VsAgentMcpConfig):
235
+ super().__init__(config)
236
+
237
+ def run(self) -> None:
238
+ ... # start your MCP server here
239
+
240
+
241
+ VsMcpServerFactory.register("my-mcp", MyCustomMcpServer)
242
+ ```
243
+
244
+ `startup()` and `shutdown()` on the base class iterate `@mcp_startup` and `@mcp_shutdown` hooks. Always call `super().startup()` and `super().shutdown()` to keep lifecycle hooks running.
245
+
246
+ ---
247
+
248
+ ## VsMcpServerFactory
249
+
250
+ Registry that maps string keys to `VsMcpServer` subclasses. Used to resolve and instantiate a server at startup without hardcoding the implementation.
251
+
252
+ ```python
253
+ from vs_mcp_agent.factory.vs_mcp_server_factory import VsMcpServerFactory
254
+
255
+ # register a custom implementation
256
+ VsMcpServerFactory.register("my-mcp", MyCustomMcpServer)
257
+
258
+ # get an instance — factory constructs VsAgentMcpConfig(config) internally
259
+ server = VsMcpServerFactory.get("my-mcp", config)
260
+ server.run()
261
+ ```
262
+
263
+ `VsFastMcpServer` registers itself as `"fastmcp"` automatically when its module is imported — no manual `register()` call needed.
264
+
265
+ ```python
266
+ from vs_mcp_agent.server.vs_fast_mcp_server import VsFastMcpServer # noqa — triggers auto-register
267
+
268
+ server = VsMcpServerFactory.get("fastmcp", config)
269
+ ```
270
+
271
+ ---
272
+
273
+ ## @mcp_server
274
+
275
+ Decorator that marks a class as a named MCP server implementation. Validates that the class extends `VsMcpServer`.
276
+
277
+ ```python
278
+ from vs_mcp_agent.decorator.vs_mcp_server_decorator import mcp_server
279
+
280
+ @mcp_server("my-mcp")
281
+ class MyCustomMcpServer(VsMcpServer):
282
+ ...
283
+ ```
284
+
285
+ Sets `cls._vs_mcp_server_key = key` on the class. Used by `mcp_server_registry` when scanning packages.
286
+
287
+ ---
288
+
289
+ ## mcp_server_registry
290
+
291
+ Scans packages for `@mcp_server`-decorated classes and `@mcp_startup`/`@mcp_shutdown` hooks, and auto-registers them. Works as a decorator on the `main()` function or as a plain call. All parameters are optional.
292
+
293
+ ```python
294
+ from vs_mcp_agent.decorator.vs_mcp_server_registry import mcp_server_registry
295
+
296
+ # as a decorator
297
+ @mcp_server_registry(server="my_pkg.server", hooks="my_pkg.lifecycle")
298
+ def main():
299
+ ...
300
+
301
+ # as a plain call
302
+ mcp_server_registry(hooks="my_agent.lifecycle")
303
+ ```
304
+
305
+ | Parameter | Description |
306
+ |---|---|
307
+ | `server` | Package path to scan for `@mcp_server`-decorated classes. Use for custom server implementations. Not needed for `"fastmcp"` — it auto-registers on import. |
308
+ | `hooks` | Package path to scan for `@mcp_startup` / `@mcp_shutdown` functions. Importing the module triggers self-registration. |
309
+
310
+ ---
311
+
312
+ ## Lifecycle Hooks
313
+
314
+ Use `@mcp_startup` and `@mcp_shutdown` to run code when the MCP server starts and stops — loading tool indexes, closing connections, etc.
315
+
316
+ ```python
317
+ from vs_mcp_agent.lifecycle.vs_mcp_lifecycle import mcp_startup, mcp_shutdown
318
+
319
+ @mcp_startup
320
+ async def load_tool_index():
321
+ await ToolIndex.load()
322
+
323
+ @mcp_shutdown
324
+ async def close_connections():
325
+ await ToolIndex.close()
326
+ ```
327
+
328
+ Hooks self-register into global lists at import time. They are executed in registration order. Both sync and async hooks are supported.
329
+
330
+ Register hook modules with `mcp_server_registry` so they are imported before the server starts:
331
+
332
+ ```python
333
+ @mcp_server_registry(hooks="my_agent.lifecycle")
334
+ def main():
335
+ ...
336
+ ```
337
+
338
+ `VsMcpServer.startup()` iterates `_mcp_startup_hooks`. `VsMcpServer.shutdown()` iterates `_mcp_shutdown_hooks`.
339
+
340
+ ---
341
+
342
+ ## Tools
343
+
344
+ Tools are actions the agent can perform. The MCP client (orchestrator or Claude) calls tools to delegate work.
345
+
346
+ ### Function-style (stateless)
347
+
348
+ ```python
349
+ from vs_mcp_agent.decorator.tool import tool
350
+ from vs_mcp_agent.schema.vs_tool_response import VsToolResponse
351
+
352
+ @tool(name="search_docs", description="Search VS library documentation")
353
+ async def search_docs(query: str) -> VsToolResponse:
354
+ results = await _do_search(query)
355
+ return VsToolResponse(status="success", result=results, summary="Found results")
356
+ ```
357
+
358
+ ### Class-style (stateful, with dependency injection)
359
+
360
+ Use when the tool needs a client, config, or any injected dependency.
361
+
362
+ ```python
363
+ from pydantic import BaseModel
364
+ from vs_mcp_agent.base.vs_base_tool import VsBaseTool
365
+ from vs_mcp_agent.decorator.tool import tool
366
+ from vs_mcp_agent.schema.vs_tool_response import VsToolResponse
367
+
368
+ @tool(name="search_docs", description="Search VS library documentation")
369
+ class SearchDocsTool(VsBaseTool):
370
+
371
+ class Input(BaseModel):
372
+ query: str
373
+
374
+ def __init__(self):
375
+ self._client = MySearchClient()
376
+
377
+ async def execute(self, input: Input) -> VsToolResponse:
378
+ results = await self._client.search(input.query)
379
+ return VsToolResponse(status="success", result=results, summary="Found results")
380
+ ```
381
+
382
+ Both styles self-register in `VsToolRegistry` at import time. `VsFastMcpServer` reads `VsToolRegistry` at `run()` and wires everything into FastMCP automatically.
383
+
384
+ ### @tool parameters
385
+
386
+ | Parameter | Type | Required | Default | Description |
387
+ |---|---|---|---|---|
388
+ | `name` | `str` | Yes | — | Tool name exposed to the MCP client |
389
+ | `description` | `str` | Yes | — | Shown in MCP tool list |
390
+ | `roles` | `List[str]` | No | `[]` | Allowed roles (informational — enforce via guards) |
391
+ | `guards` | `List[Callable]` | No | `[]` | Async callables run before the tool executes |
392
+
393
+ ### VsToolResponse
394
+
395
+ All tool functions must return `VsToolResponse`.
396
+
397
+ | Field | Type | Description |
398
+ |---|---|---|
399
+ | `status` | `"success"` \| `"error"` \| `"clarification"` \| `"not_supported"` | Outcome signal for the orchestrator |
400
+ | `result` | `Optional[Any]` | Return payload on success |
401
+ | `summary` | `str` | Human-readable summary of what happened |
402
+ | `confidence` | `Optional[float]` | Confidence score (0–1) |
403
+ | `artifacts_extracted` | `List[ArtifactEntry]` | Structured data extracted during execution |
404
+ | `user_preferences_extracted` | `List[UserPreferenceEntry]` | User preferences inferred during execution |
405
+ | `active_highlights` | `List[ActiveHighlight]` | Key highlights from the result |
406
+ | `tokens_consumed` | `int` | Token count if the tool called an LLM |
407
+ | `error` | `Optional[ErrorDetail]` | Error detail when `status="error"` |
408
+ | `clarification_needed` | `Optional[ClarificationDetail]` | Clarification questions when `status="clarification"` |
409
+ | `not_supported` | `Optional[NotSupportedDetail]` | Reason when `status="not_supported"` |
410
+
411
+ **Status values:**
412
+
413
+ | Status | When to use |
414
+ |---|---|
415
+ | `success` | Tool completed successfully |
416
+ | `error` | Tool failed — populate `error` with `ErrorDetail` |
417
+ | `clarification` | Need more input from the user — populate `clarification_needed` |
418
+ | `not_supported` | Request is out of scope — populate `not_supported` with a reason |
419
+
420
+ ---
421
+
422
+ ## Resources
423
+
424
+ Resources expose read-only data — documents, schemas, configs — that the MCP client can fetch by URI.
425
+
426
+ ```python
427
+ from pydantic import BaseModel
428
+ from vs_mcp_agent.base.vs_base_resource import VsBaseResource
429
+ from vs_mcp_agent.decorator.resource import resource
430
+ from vs_mcp_agent.schema.vs_resource_response import VsResourceResponse
431
+
432
+ @resource(uri="docs://api-schema", description="Returns the API schema document")
433
+ class ApiSchemaResource(VsBaseResource):
434
+
435
+ class Input(BaseModel):
436
+ version: str = "latest"
437
+
438
+ async def fetch(self, input: Input) -> VsResourceResponse:
439
+ schema = await _load_schema(input.version)
440
+ return VsResourceResponse(status="success", result=schema)
441
+ ```
442
+
443
+ `@resource` parameters:
444
+
445
+ | Parameter | Type | Required | Description |
446
+ |---|---|---|---|
447
+ | `uri` | `str` | Yes | URI the MCP client uses to fetch this resource |
448
+ | `description` | `str` | Yes | Shown in MCP resource list |
449
+
450
+ `VsResourceResponse` fields:
451
+
452
+ | Field | Type | Description |
453
+ |---|---|---|
454
+ | `status` | `"success"` \| `"error"` \| `"not_supported"` | Outcome |
455
+ | `result` | `Optional[Any]` | Resource data on success |
456
+ | `error` | `Optional[ErrorDetail]` | Error detail when `status="error"` |
457
+ | `not_supported` | `Optional[NotSupportedDetail]` | Reason when `status="not_supported"` |
458
+
459
+ ---
460
+
461
+ ## Prompts
462
+
463
+ Prompts expose reusable prompt templates that the MCP client can request by name.
464
+
465
+ ```python
466
+ from pydantic import BaseModel
467
+ from vs_mcp_agent.base.vs_base_prompt import VsBasePrompt
468
+ from vs_mcp_agent.decorator.prompt import prompt
469
+ from vs_mcp_agent.schema.vs_prompt_response import VsPromptResponse
470
+
471
+ @prompt(name="summarise_doc", description="Prompt to summarise a document")
472
+ class SummariseDocPrompt(VsBasePrompt):
473
+
474
+ class Input(BaseModel):
475
+ topic: str
476
+ length: str = "short"
477
+
478
+ async def generate(self, input: Input) -> VsPromptResponse:
479
+ text = f"Summarise the following in {input.length} form. Topic: {input.topic}"
480
+ return VsPromptResponse(status="success", prompt=text)
481
+ ```
482
+
483
+ `@prompt` parameters:
484
+
485
+ | Parameter | Type | Required | Description |
486
+ |---|---|---|---|
487
+ | `name` | `str` | Yes | Prompt name exposed to the MCP client |
488
+ | `description` | `str` | Yes | Shown in MCP prompt list |
489
+
490
+ `VsPromptResponse` fields:
491
+
492
+ | Field | Type | Description |
493
+ |---|---|---|
494
+ | `status` | `"success"` \| `"error"` | Outcome |
495
+ | `prompt` | `Optional[str]` | The rendered prompt string on success |
496
+ | `error` | `Optional[ErrorDetail]` | Error detail when `status="error"` |
497
+
498
+ ---
499
+
500
+ ## Authorization
501
+
502
+ ### VsMcpSecurity
503
+
504
+ Guard for MCP-only tools. Reads the auth context set by `VsMcpAuthMiddleware` after JWT verification.
505
+
506
+ ```python
507
+ from vs_mcp_agent.auth.vs_mcp_security import VsMcpSecurity
508
+
509
+ @tool(name="admin_tool", description="Admin only", guards=[VsMcpSecurity(roles=["admin"])])
510
+ async def admin_tool(query: str) -> VsToolResponse:
511
+ ...
512
+ ```
513
+
514
+ **No roles (auth only):**
515
+
516
+ ```python
517
+ guards=[VsMcpSecurity()] # rejects unauthenticated callers, any role allowed
518
+ ```
519
+
520
+ **Multiple roles (any match):**
521
+
522
+ ```python
523
+ guards=[VsMcpSecurity(roles=["admin", "editor"])] # passes if caller has admin OR editor
524
+ ```
525
+
526
+ Enable auth middleware by setting `agent.auth_enabled = true` in `config.ini`. `VsFastMcpServer` wires `VsMcpAuthMiddleware` automatically when auth is enabled.
527
+
528
+ ### Custom guards
529
+
530
+ Any async callable that raises `PermissionError` on failure works as a guard:
531
+
532
+ ```python
533
+ async def require_verified_user():
534
+ ctx = get_auth_context()
535
+ if not ctx or not ctx.is_verified:
536
+ raise PermissionError("Verified account required")
537
+
538
+ @tool(name="sensitive_tool", description="Verified users only",
539
+ guards=[VsMcpSecurity(roles=["user"]), require_verified_user])
540
+ async def sensitive_tool(query: str) -> VsToolResponse:
541
+ ...
542
+ ```
543
+
544
+ Multiple guards run in order — all must pass before the tool executes.
545
+
546
+ ### VsMcpAuthMiddleware
547
+
548
+ Starlette middleware that validates JWT tokens on every incoming request (except `/.well-known/` paths). Sets the auth context so `VsMcpSecurity` and `get_auth_context()` work inside tools.
549
+
550
+ `VsFastMcpServer` registers `VsMcpAuthMiddleware` automatically when `agent.auth_enabled = true`. You do not need to wire it manually.
551
+
552
+ ```python
553
+ from vs_mcp_agent.auth.vs_mcp_auth import get_auth_context
554
+
555
+ @tool(name="whoami", description="Return caller identity")
556
+ async def whoami() -> VsToolResponse:
557
+ ctx = get_auth_context()
558
+ return VsToolResponse(status="success", result={"username": ctx.username})
559
+ ```
560
+
561
+ ---
562
+
563
+ ## Built-in Tools
564
+
565
+ Every `VsFastMcpServer` instance registers two tools automatically:
566
+
567
+ | Tool | Description |
568
+ |---|---|
569
+ | `ping` | Liveness check. Returns `{"pong": true, "timestamp": "..."}`. |
570
+ | `health` | Returns agent name, version, transport, and uptime in seconds. |
571
+
572
+ These are always available to MCP clients without any configuration.
573
+
574
+ ---
575
+
576
+ ## Extending vs-mcp-agent
577
+
578
+ ### Adding a custom MCP server implementation
579
+
580
+ Implement `VsMcpServer`, decorate with `@mcp_server`, and register:
581
+
582
+ ```python
583
+ from vs_mcp_agent.server.vs_mcp_server import VsMcpServer
584
+ from vs_mcp_agent.config.vs_agent_mcp_config import VsAgentMcpConfig
585
+ from vs_mcp_agent.decorator.vs_mcp_server_decorator import mcp_server
586
+ from vs_mcp_agent.factory.vs_mcp_server_factory import VsMcpServerFactory
587
+
588
+
589
+ @mcp_server("my-mcp")
590
+ class MyMcpServer(VsMcpServer):
591
+
592
+ def __init__(self, config: VsAgentMcpConfig):
593
+ super().__init__(config)
594
+
595
+ def run(self) -> None:
596
+ self._wire_tools()
597
+ # start your server
598
+
599
+ def _wire_tools(self) -> None:
600
+ from vs_mcp_agent.registry.vs_tool_registry import VsToolRegistry
601
+ for name, entry in VsToolRegistry.get_all().items():
602
+ ... # register into your server
603
+
604
+
605
+ VsMcpServerFactory.register("my-mcp", MyMcpServer)
606
+ ```
607
+
608
+ Then use it by key:
609
+
610
+ ```python
611
+ server = VsMcpServerFactory.get("my-mcp", config)
612
+ server.run()
613
+ ```
614
+
615
+ Or scan the package automatically:
616
+
617
+ ```python
618
+ @mcp_server_registry(server="my_pkg.server")
619
+ def main():
620
+ server = VsMcpServerFactory.get("my-mcp", config)
621
+ server.run()
622
+ ```
623
+
624
+ ---
625
+
626
+ ## Error Handling
627
+
628
+ Exceptions raised inside tool, resource, or prompt functions are caught by `VsFastMcpServer` and returned as MCP error results — the server does not crash.
629
+
630
+ For expected failures, use `VsToolResponse` with `status="error"` rather than raising:
631
+
632
+ ```python
633
+ from vs_mcp_agent.schema.vs_tool_response import VsToolResponse, ErrorDetail
634
+
635
+ @tool(name="get_doc", description="Fetch a document by ID")
636
+ async def get_doc(doc_id: str) -> VsToolResponse:
637
+ doc = await repo.find(doc_id)
638
+ if doc is None:
639
+ return VsToolResponse(
640
+ status="error",
641
+ error=ErrorDetail(code="NOT_FOUND", message=f"Document '{doc_id}' not found", retryable=False),
642
+ )
643
+ return VsToolResponse(status="success", result=doc)
644
+ ```
645
+
646
+ ---
647
+
648
+ ## Class Reference
649
+
650
+ ---
651
+
652
+ ### VsMcpServer
653
+
654
+ Abstract base class for all MCP server implementations.
655
+
656
+ **Constructor:**
657
+
658
+ | Parameter | Type | Description |
659
+ |---|---|---|
660
+ | `config` | `VsAgentMcpConfig` | Parsed MCP configuration |
661
+
662
+ **Methods:**
663
+
664
+ | Method | Signature | Description |
665
+ |---|---|---|
666
+ | `startup` | `async startup() -> None` | Runs `@mcp_startup` hooks in order. Call `super().startup()` to keep hook execution. |
667
+ | `shutdown` | `async shutdown() -> None` | Runs `@mcp_shutdown` hooks in order. Call `super().shutdown()` to keep hook execution. |
668
+ | `run` | `run() -> None` | Abstract. Start the MCP server. |
669
+
670
+ ---
671
+
672
+ ### VsMcpServerFactory
673
+
674
+ Registry mapping string keys to `VsMcpServer` subclasses. Thread-safe class-level dict.
675
+
676
+ **Methods:**
677
+
678
+ | Method | Signature | Description |
679
+ |---|---|---|
680
+ | `register` | `register(key: str, server_class: Type[VsMcpServer]) -> None` | Register a server class. Raises `TypeError` if not a `VsMcpServer` subclass. |
681
+ | `get` | `get(key: str, config: VsBaseConfig) -> VsMcpServer` | Construct and return a server instance. Raises `KeyError` if key not registered. Wraps `config` in `VsAgentMcpConfig` internally. |
682
+
683
+ **Notes:**
684
+ - `VsFastMcpServer` auto-registers as `"fastmcp"` when its module is imported.
685
+ - Import `VsFastMcpServer` before calling `VsMcpServerFactory.get("fastmcp", config)`.
686
+
687
+ ---
688
+
689
+ ### VsAgentMcpConfig
690
+
691
+ Parsed MCP configuration. Constructed from `VsBaseConfig` by reading the `[agent]` section.
692
+
693
+ **Fields:**
694
+
695
+ | Field | Type | Default | Description |
696
+ |---|---|---|---|
697
+ | `name` | `str` | `vs-agent` | MCP server name |
698
+ | `version` | `str` | `0.1.0` | MCP server version |
699
+ | `description` | `str` | `""` | MCP server description |
700
+ | `transport` | `str` | `streamable-http` | Transport type |
701
+ | `host` | `str` | `0.0.0.0` | Bind host |
702
+ | `port` | `int` | `8080` | Bind port |
703
+ | `auth_enabled` | `bool` | `False` | Enable JWT auth middleware |
704
+
705
+ **Notes:**
706
+ - Raises `ValueError` on construction if `transport` is not one of `stdio`, `sse`, `streamable-http`.
707
+
708
+ ---
709
+
710
+ ### VsFastMcpServer
711
+
712
+ Built-in `VsMcpServer` implementation backed by FastMCP. Auto-registers as `"fastmcp"` on import.
713
+
714
+ **Behaviour at `run()`:**
715
+ 1. Reads `VsToolRegistry`, `VsResourceRegistry`, `VsPromptRegistry` and wires all entries into FastMCP.
716
+ 2. Applies guard proxies to guarded tools.
717
+ 3. Registers built-in `ping` and `health` tools.
718
+ 4. If `auth_enabled`, wires `VsMcpAuthMiddleware`.
719
+ 5. For `stdio` transport: calls `mcp.run(transport="stdio")`.
720
+ 6. For HTTP transports: wraps the ASGI lifespan to call `startup()`/`shutdown()`, then starts uvicorn.
721
+
722
+ **Notes:**
723
+ - Requires `fastmcp` and `uvicorn` — install with `pip install vs-mcp-agent[fastmcp]`.
724
+ - `mcp` property exposes the underlying `FastMCP` instance for advanced configuration.
725
+
726
+ ---
727
+
728
+ ### `@mcp_server`
729
+
730
+ Decorator. Marks a class as a named MCP server implementation. Validates that the class extends `VsMcpServer`.
731
+
732
+ **Parameters:**
733
+
734
+ | Parameter | Type | Required | Description |
735
+ |---|---|---|---|
736
+ | `key` | `str` | Yes | Registry key used with `VsMcpServerFactory.get(key, config)` |
737
+
738
+ ---
739
+
740
+ ### `mcp_server_registry`
741
+
742
+ Scans packages for `@mcp_server` classes and `@mcp_startup`/`@mcp_shutdown` functions and registers them. Works as a function decorator or a plain call.
743
+
744
+ **Parameters:**
745
+
746
+ | Parameter | Type | Description |
747
+ |---|---|---|
748
+ | `server` | `Optional[str]` | Package path to scan for `@mcp_server` classes |
749
+ | `hooks` | `Optional[str]` | Package path to scan for lifecycle hook functions |
750
+
751
+ ---
752
+
753
+ ### `@mcp_startup` / `@mcp_shutdown`
754
+
755
+ Decorators. Register a function into the global `_mcp_startup_hooks` or `_mcp_shutdown_hooks` list at import time. Both sync and async functions are supported.
756
+
757
+ ```python
758
+ from vs_mcp_agent.lifecycle.vs_mcp_lifecycle import mcp_startup, mcp_shutdown
759
+
760
+ @mcp_startup
761
+ async def on_start():
762
+ ...
763
+
764
+ @mcp_shutdown
765
+ def on_stop():
766
+ ...
767
+ ```
768
+
769
+ Hooks are called in registration order by `VsMcpServer.startup()` and `VsMcpServer.shutdown()`.
770
+
771
+ ---
772
+
773
+ ### `@tool`
774
+
775
+ Decorator. Registers a function or class in `VsToolRegistry` at import time.
776
+
777
+ **Parameters:**
778
+
779
+ | Parameter | Type | Required | Default | Description |
780
+ |---|---|---|---|---|
781
+ | `name` | `str` | Yes | — | Tool name exposed to the MCP client |
782
+ | `description` | `str` | Yes | — | Shown in MCP tool list |
783
+ | `roles` | `List[str]` | No | `[]` | Allowed roles (informational) |
784
+ | `guards` | `List[Callable]` | No | `[]` | Async callables run before the tool executes |
785
+
786
+ **Notes:**
787
+ - Each `name` must be unique. Duplicate names raise `ValueError`.
788
+ - Class-style: target must extend `VsBaseTool` and implement `execute(input) -> VsToolResponse`.
789
+ - Function-style: target must be an async callable.
790
+
791
+ ---
792
+
793
+ ### VsBaseTool
794
+
795
+ Abstract base class for class-style tools.
796
+
797
+ **Inner class:**
798
+
799
+ | Name | Description |
800
+ |---|---|
801
+ | `Input` | Pydantic `BaseModel` subclass. Define fields here for typed input. |
802
+
803
+ **Methods:**
804
+
805
+ | Method | Signature | Description |
806
+ |---|---|---|
807
+ | `execute` | `async execute(input: Input) -> VsToolResponse` | Abstract. Implement tool logic here. |
808
+
809
+ ---
810
+
811
+ ### `@resource`
812
+
813
+ Decorator. Registers a class in `VsResourceRegistry` at import time.
814
+
815
+ **Parameters:**
816
+
817
+ | Parameter | Type | Required | Description |
818
+ |---|---|---|---|
819
+ | `uri` | `str` | Yes | URI the MCP client uses to fetch this resource |
820
+ | `description` | `str` | Yes | Shown in MCP resource list |
821
+
822
+ **Notes:**
823
+ - Target must extend `VsBaseResource` and implement `fetch(input) -> VsResourceResponse`.
824
+ - Each `uri` must be unique. Duplicate URIs raise `ValueError`.
825
+
826
+ ---
827
+
828
+ ### VsBaseResource
829
+
830
+ Abstract base class for resources.
831
+
832
+ **Inner class:**
833
+
834
+ | Name | Description |
835
+ |---|---|
836
+ | `Input` | Pydantic `BaseModel` subclass. Define fields for fetch parameters. |
837
+
838
+ **Methods:**
839
+
840
+ | Method | Signature | Description |
841
+ |---|---|---|
842
+ | `fetch` | `async fetch(input: Input) -> VsResourceResponse` | Abstract. Return the resource data. |
843
+
844
+ ---
845
+
846
+ ### `@prompt`
847
+
848
+ Decorator. Registers a class in `VsPromptRegistry` at import time.
849
+
850
+ **Parameters:**
851
+
852
+ | Parameter | Type | Required | Description |
853
+ |---|---|---|---|
854
+ | `name` | `str` | Yes | Prompt name exposed to the MCP client |
855
+ | `description` | `str` | Yes | Shown in MCP prompt list |
856
+
857
+ **Notes:**
858
+ - Target must extend `VsBasePrompt` and implement `generate(input) -> VsPromptResponse`.
859
+ - Each `name` must be unique. Duplicate names raise `ValueError`.
860
+
861
+ ---
862
+
863
+ ### VsBasePrompt
864
+
865
+ Abstract base class for prompts.
866
+
867
+ **Inner class:**
868
+
869
+ | Name | Description |
870
+ |---|---|
871
+ | `Input` | Pydantic `BaseModel` subclass. Define fields for prompt parameters. |
872
+
873
+ **Methods:**
874
+
875
+ | Method | Signature | Description |
876
+ |---|---|---|
877
+ | `generate` | `async generate(input: Input) -> VsPromptResponse` | Abstract. Return the rendered prompt. |
878
+
879
+ ---
880
+
881
+ ### VsMcpSecurity
882
+
883
+ Guard for MCP-only tools. Reads the auth context set by `VsMcpAuthMiddleware`.
884
+
885
+ **Constructor:**
886
+
887
+ | Parameter | Type | Default | Description |
888
+ |---|---|---|---|
889
+ | `roles` | `Optional[List[str]]` | `[]` | Required roles. Empty = any authenticated caller. |
890
+
891
+ **Behaviour:**
892
+
893
+ | Condition | Result |
894
+ |---|---|
895
+ | No auth context | Raises `PermissionError("Unauthenticated request")` |
896
+ | Auth context present, no roles required | Passes |
897
+ | Auth context present, caller has required role | Passes |
898
+ | Auth context present, caller lacks required role | Raises `PermissionError` |
899
+
900
+ ---
901
+
902
+ ### VsMcpAuthMiddleware
903
+
904
+ Starlette middleware. Validates `Bearer` JWT tokens on every request and sets the auth context.
905
+
906
+ **Constructor:**
907
+
908
+ | Parameter | Type | Description |
909
+ |---|---|---|
910
+ | `app` | ASGI app | The wrapped application |
911
+ | `jwt_provider` | `VsJWTProvider` | JWT verifier from `vs-security` |
912
+
913
+ **Notes:**
914
+ - `VsFastMcpServer` registers this automatically when `agent.auth_enabled = true`.
915
+ - Paths starting with `/.well-known/` are whitelisted and bypass auth.
916
+ - Sets both the MCP-local context (`_auth_context_var`) and the shared VS context (`set_auth_context`) so guards work uniformly across protocols.
917
+
918
+ ---
919
+
920
+ ### VsToolRegistry
921
+
922
+ Class-level registry of all `@tool`-registered functions and classes. Thread-safe.
923
+
924
+ **Methods:**
925
+
926
+ | Method | Signature | Description |
927
+ |---|---|---|
928
+ | `register_class` | `register_class(name, description, tool_class, roles, guards) -> None` | Register a class-style tool. Raises `TypeError` if not a `VsBaseTool` subclass. |
929
+ | `register_fn` | `register_fn(name, description, fn, roles, guards) -> None` | Register a function-style tool. |
930
+ | `get_all` | `get_all() -> Dict[str, _ToolEntry]` | Returns a snapshot of all registered tools. |
931
+
932
+ ---
933
+
934
+ ### VsResourceRegistry
935
+
936
+ Class-level registry of all `@resource`-registered classes. Thread-safe.
937
+
938
+ **Methods:**
939
+
940
+ | Method | Signature | Description |
941
+ |---|---|---|
942
+ | `register` | `register(uri, description, resource_class) -> None` | Register a resource class. Raises `TypeError` if not a `VsBaseResource` subclass. |
943
+ | `get_all` | `get_all() -> Dict[str, _ResourceEntry]` | Returns a snapshot of all registered resources. |
944
+
945
+ ---
946
+
947
+ ### VsPromptRegistry
948
+
949
+ Class-level registry of all `@prompt`-registered classes. Thread-safe.
950
+
951
+ **Methods:**
952
+
953
+ | Method | Signature | Description |
954
+ |---|---|---|
955
+ | `register` | `register(name, description, prompt_class) -> None` | Register a prompt class. Raises `TypeError` if not a `VsBasePrompt` subclass. |
956
+ | `get_all` | `get_all() -> Dict[str, _PromptEntry]` | Returns a snapshot of all registered prompts. |