agno 2.3.16__py3-none-any.whl → 2.3.17__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. agno/agent/__init__.py +2 -0
  2. agno/agent/agent.py +4 -53
  3. agno/agent/remote.py +351 -0
  4. agno/client/__init__.py +3 -0
  5. agno/client/os.py +2669 -0
  6. agno/db/base.py +20 -0
  7. agno/db/mongo/async_mongo.py +11 -0
  8. agno/db/mongo/mongo.py +10 -0
  9. agno/db/mysql/async_mysql.py +9 -0
  10. agno/db/mysql/mysql.py +9 -0
  11. agno/db/postgres/async_postgres.py +9 -0
  12. agno/db/postgres/postgres.py +9 -0
  13. agno/db/postgres/utils.py +3 -2
  14. agno/db/sqlite/async_sqlite.py +9 -0
  15. agno/db/sqlite/sqlite.py +11 -1
  16. agno/exceptions.py +23 -0
  17. agno/knowledge/chunking/semantic.py +123 -46
  18. agno/knowledge/reader/csv_reader.py +1 -1
  19. agno/knowledge/reader/field_labeled_csv_reader.py +1 -1
  20. agno/knowledge/reader/json_reader.py +1 -1
  21. agno/os/app.py +104 -23
  22. agno/os/auth.py +25 -1
  23. agno/os/interfaces/a2a/a2a.py +7 -6
  24. agno/os/interfaces/a2a/router.py +13 -13
  25. agno/os/interfaces/agui/agui.py +5 -3
  26. agno/os/interfaces/agui/router.py +23 -16
  27. agno/os/interfaces/base.py +7 -7
  28. agno/os/interfaces/slack/router.py +6 -6
  29. agno/os/interfaces/slack/slack.py +7 -7
  30. agno/os/interfaces/whatsapp/router.py +29 -6
  31. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  32. agno/os/managers.py +326 -0
  33. agno/os/mcp.py +651 -79
  34. agno/os/router.py +125 -18
  35. agno/os/routers/agents/router.py +65 -22
  36. agno/os/routers/agents/schema.py +16 -4
  37. agno/os/routers/database.py +5 -0
  38. agno/os/routers/evals/evals.py +93 -11
  39. agno/os/routers/evals/utils.py +6 -6
  40. agno/os/routers/knowledge/knowledge.py +104 -16
  41. agno/os/routers/memory/memory.py +124 -7
  42. agno/os/routers/metrics/metrics.py +21 -4
  43. agno/os/routers/session/session.py +141 -12
  44. agno/os/routers/teams/router.py +40 -14
  45. agno/os/routers/teams/schema.py +12 -4
  46. agno/os/routers/traces/traces.py +54 -4
  47. agno/os/routers/workflows/router.py +223 -117
  48. agno/os/routers/workflows/schema.py +65 -1
  49. agno/os/schema.py +38 -12
  50. agno/os/utils.py +87 -166
  51. agno/remote/__init__.py +3 -0
  52. agno/remote/base.py +484 -0
  53. agno/run/workflow.py +1 -0
  54. agno/team/__init__.py +2 -0
  55. agno/team/remote.py +287 -0
  56. agno/team/team.py +25 -54
  57. agno/tracing/exporter.py +10 -6
  58. agno/tracing/setup.py +2 -1
  59. agno/utils/agent.py +58 -1
  60. agno/utils/http.py +68 -20
  61. agno/utils/os.py +0 -0
  62. agno/utils/remote.py +23 -0
  63. agno/vectordb/chroma/chromadb.py +452 -16
  64. agno/vectordb/pgvector/pgvector.py +7 -0
  65. agno/vectordb/redis/redisdb.py +1 -1
  66. agno/workflow/__init__.py +2 -0
  67. agno/workflow/agent.py +2 -2
  68. agno/workflow/remote.py +222 -0
  69. agno/workflow/types.py +0 -73
  70. agno/workflow/workflow.py +119 -68
  71. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/METADATA +1 -1
  72. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/RECORD +75 -65
  73. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/WHEEL +0 -0
  74. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/licenses/LICENSE +0 -0
  75. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/top_level.txt +0 -0
agno/os/mcp.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Router for MCP interface providing Model Context Protocol endpoints."""
2
2
 
3
3
  import logging
4
- from typing import TYPE_CHECKING, List, Optional, cast
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
5
5
  from uuid import uuid4
6
6
 
7
7
  from fastmcp import FastMCP
@@ -9,17 +9,23 @@ from fastmcp.server.http import (
9
9
  StarletteWithLifespan,
10
10
  )
11
11
 
12
- from agno.db.base import AsyncBaseDb, SessionType
12
+ from agno.db.base import AsyncBaseDb, BaseDb, SessionType
13
13
  from agno.db.schemas import UserMemory
14
14
  from agno.os.routers.memory.schemas import (
15
15
  UserMemorySchema,
16
16
  )
17
17
  from agno.os.schema import (
18
+ AgentSessionDetailSchema,
18
19
  AgentSummaryResponse,
19
20
  ConfigResponse,
20
21
  InterfaceResponse,
22
+ RunSchema,
21
23
  SessionSchema,
24
+ TeamRunSchema,
25
+ TeamSessionDetailSchema,
22
26
  TeamSummaryResponse,
27
+ WorkflowRunSchema,
28
+ WorkflowSessionDetailSchema,
23
29
  WorkflowSummaryResponse,
24
30
  )
25
31
  from agno.os.utils import (
@@ -28,9 +34,11 @@ from agno.os.utils import (
28
34
  get_team_by_id,
29
35
  get_workflow_by_id,
30
36
  )
37
+ from agno.remote.base import RemoteDb
31
38
  from agno.run.agent import RunOutput
32
39
  from agno.run.team import TeamRunOutput
33
40
  from agno.run.workflow import WorkflowRunOutput
41
+ from agno.session import AgentSession, TeamSession, WorkflowSession
34
42
 
35
43
  if TYPE_CHECKING:
36
44
  from agno.os.app import AgentOS
@@ -74,128 +82,531 @@ def get_mcp_server(
74
82
  ],
75
83
  )
76
84
 
77
- @mcp.tool(name="run_agent", description="Run an agent", tags={"core"}) # type: ignore
85
+ # ==================== Core Run Tools ====================
86
+
87
+ @mcp.tool(name="run_agent", description="Run an agent with a message", tags={"core"}) # type: ignore
78
88
  async def run_agent(agent_id: str, message: str) -> RunOutput:
79
89
  agent = get_agent_by_id(agent_id, os.agents)
80
90
  if agent is None:
81
91
  raise Exception(f"Agent {agent_id} not found")
82
92
  return await agent.arun(message)
83
93
 
84
- @mcp.tool(name="run_team", description="Run a team", tags={"core"}) # type: ignore
94
+ @mcp.tool(name="run_team", description="Run a team with a message", tags={"core"}) # type: ignore
85
95
  async def run_team(team_id: str, message: str) -> TeamRunOutput:
86
96
  team = get_team_by_id(team_id, os.teams)
87
97
  if team is None:
88
98
  raise Exception(f"Team {team_id} not found")
89
99
  return await team.arun(message)
90
100
 
91
- @mcp.tool(name="run_workflow", description="Run a workflow", tags={"core"}) # type: ignore
101
+ @mcp.tool(name="run_workflow", description="Run a workflow with a message", tags={"core"}) # type: ignore
92
102
  async def run_workflow(workflow_id: str, message: str) -> WorkflowRunOutput:
93
103
  workflow = get_workflow_by_id(workflow_id, os.workflows)
94
104
  if workflow is None:
95
105
  raise Exception(f"Workflow {workflow_id} not found")
96
106
  return await workflow.arun(message)
97
107
 
98
- # Session Management Tools
99
- @mcp.tool(name="get_sessions_for_agent", description="Get list of sessions for an agent", tags={"session"}) # type: ignore
100
- async def get_sessions_for_agent(
101
- agent_id: str,
108
+ # ==================== Session Management Tools ====================
109
+
110
+ @mcp.tool(
111
+ name="get_sessions",
112
+ description="Get paginated list of sessions with optional filtering by type, component, user, and name",
113
+ tags={"session"},
114
+ ) # type: ignore
115
+ async def get_sessions(
102
116
  db_id: str,
117
+ session_type: str = "agent",
118
+ component_id: Optional[str] = None,
103
119
  user_id: Optional[str] = None,
120
+ session_name: Optional[str] = None,
121
+ limit: int = 20,
122
+ page: int = 1,
104
123
  sort_by: str = "created_at",
105
124
  sort_order: str = "desc",
106
- ):
125
+ ) -> Dict[str, Any]:
107
126
  db = await get_db(os.dbs, db_id)
127
+ session_type_enum = SessionType(session_type)
128
+ if isinstance(db, RemoteDb):
129
+ result = await db.get_sessions(
130
+ session_type=session_type_enum,
131
+ component_id=component_id,
132
+ user_id=user_id,
133
+ session_name=session_name,
134
+ limit=limit,
135
+ page=page,
136
+ sort_by=sort_by,
137
+ sort_order=sort_order,
138
+ db_id=db_id,
139
+ )
140
+ return result.model_dump()
141
+
108
142
  if isinstance(db, AsyncBaseDb):
109
143
  db = cast(AsyncBaseDb, db)
110
- sessions = await db.get_sessions(
111
- session_type=SessionType.AGENT,
112
- component_id=agent_id,
144
+ sessions, total_count = await db.get_sessions(
145
+ session_type=session_type_enum,
146
+ component_id=component_id,
113
147
  user_id=user_id,
148
+ session_name=session_name,
149
+ limit=limit,
150
+ page=page,
114
151
  sort_by=sort_by,
115
152
  sort_order=sort_order,
116
153
  deserialize=False,
117
154
  )
118
155
  else:
119
- sessions = db.get_sessions(
120
- session_type=SessionType.AGENT,
121
- component_id=agent_id,
156
+ sessions, total_count = db.get_sessions(
157
+ session_type=session_type_enum,
158
+ component_id=component_id,
122
159
  user_id=user_id,
160
+ session_name=session_name,
161
+ limit=limit,
162
+ page=page,
123
163
  sort_by=sort_by,
124
164
  sort_order=sort_order,
125
165
  deserialize=False,
126
166
  )
127
167
 
128
168
  return {
129
- "data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
169
+ "data": [SessionSchema.from_dict(session).model_dump() for session in sessions], # type: ignore
170
+ "meta": {
171
+ "page": page,
172
+ "limit": limit,
173
+ "total_count": total_count,
174
+ "total_pages": (total_count + limit - 1) // limit if limit > 0 else 0, # type: ignore
175
+ },
130
176
  }
131
177
 
132
- @mcp.tool(name="get_sessions_for_team", description="Get list of sessions for a team", tags={"session"}) # type: ignore
133
- async def get_sessions_for_team(
134
- team_id: str,
178
+ @mcp.tool(
179
+ name="get_session",
180
+ description="Get detailed information about a specific session by ID",
181
+ tags={"session"},
182
+ ) # type: ignore
183
+ async def get_session(
184
+ session_id: str,
135
185
  db_id: str,
186
+ session_type: str = "agent",
136
187
  user_id: Optional[str] = None,
137
- sort_by: str = "created_at",
138
- sort_order: str = "desc",
139
- ):
188
+ ) -> Dict[str, Any]:
140
189
  db = await get_db(os.dbs, db_id)
190
+ session_type_enum = SessionType(session_type)
191
+
192
+ if isinstance(db, RemoteDb):
193
+ result = await db.get_session(
194
+ session_id=session_id,
195
+ session_type=session_type_enum,
196
+ user_id=user_id,
197
+ db_id=db_id,
198
+ )
199
+ return result.model_dump()
200
+
141
201
  if isinstance(db, AsyncBaseDb):
142
202
  db = cast(AsyncBaseDb, db)
143
- sessions = await db.get_sessions(
144
- session_type=SessionType.TEAM,
145
- component_id=team_id,
203
+ session = await db.get_session(session_id=session_id, session_type=session_type_enum, user_id=user_id)
204
+ else:
205
+ db = cast(BaseDb, db)
206
+ session = db.get_session(session_id=session_id, session_type=session_type_enum, user_id=user_id)
207
+
208
+ if not session:
209
+ raise Exception(f"Session {session_id} not found")
210
+
211
+ if session_type_enum == SessionType.AGENT:
212
+ return AgentSessionDetailSchema.from_session(session).model_dump() # type: ignore
213
+ elif session_type_enum == SessionType.TEAM:
214
+ return TeamSessionDetailSchema.from_session(session).model_dump() # type: ignore
215
+ else:
216
+ return WorkflowSessionDetailSchema.from_session(session).model_dump() # type: ignore
217
+
218
+ @mcp.tool(
219
+ name="create_session",
220
+ description="Create a new session for an agent, team, or workflow",
221
+ tags={"session"},
222
+ ) # type: ignore
223
+ async def create_session(
224
+ db_id: str,
225
+ session_type: str = "agent",
226
+ session_id: Optional[str] = None,
227
+ session_name: Optional[str] = None,
228
+ session_state: Optional[Dict[str, Any]] = None,
229
+ metadata: Optional[Dict[str, Any]] = None,
230
+ user_id: Optional[str] = None,
231
+ agent_id: Optional[str] = None,
232
+ team_id: Optional[str] = None,
233
+ workflow_id: Optional[str] = None,
234
+ ) -> Dict[str, Any]:
235
+ import time
236
+
237
+ db = await get_db(os.dbs, db_id)
238
+ session_type_enum = SessionType(session_type)
239
+
240
+ # Generate session_id if not provided
241
+ session_id = session_id or str(uuid4())
242
+
243
+ if isinstance(db, RemoteDb):
244
+ result = await db.create_session(
245
+ session_type=session_type_enum,
246
+ session_id=session_id,
247
+ session_name=session_name,
248
+ session_state=session_state,
249
+ metadata=metadata,
146
250
  user_id=user_id,
147
- sort_by=sort_by,
148
- sort_order=sort_order,
149
- deserialize=False,
251
+ agent_id=agent_id,
252
+ team_id=team_id,
253
+ workflow_id=workflow_id,
254
+ db_id=db_id,
255
+ )
256
+ return result.model_dump()
257
+
258
+ # Prepare session_data
259
+ session_data: Dict[str, Any] = {}
260
+ if session_state is not None:
261
+ session_data["session_state"] = session_state
262
+ if session_name is not None:
263
+ session_data["session_name"] = session_name
264
+
265
+ current_time = int(time.time())
266
+
267
+ # Create the appropriate session type
268
+ session: Union[AgentSession, TeamSession, WorkflowSession]
269
+ if session_type_enum == SessionType.AGENT:
270
+ session = AgentSession(
271
+ session_id=session_id,
272
+ agent_id=agent_id,
273
+ user_id=user_id,
274
+ session_data=session_data if session_data else None,
275
+ metadata=metadata,
276
+ created_at=current_time,
277
+ updated_at=current_time,
278
+ )
279
+ elif session_type_enum == SessionType.TEAM:
280
+ session = TeamSession(
281
+ session_id=session_id,
282
+ team_id=team_id,
283
+ user_id=user_id,
284
+ session_data=session_data if session_data else None,
285
+ metadata=metadata,
286
+ created_at=current_time,
287
+ updated_at=current_time,
150
288
  )
151
289
  else:
152
- sessions = db.get_sessions(
153
- session_type=SessionType.TEAM,
154
- component_id=team_id,
290
+ session = WorkflowSession(
291
+ session_id=session_id,
292
+ workflow_id=workflow_id,
155
293
  user_id=user_id,
156
- sort_by=sort_by,
157
- sort_order=sort_order,
158
- deserialize=False,
294
+ session_data=session_data if session_data else None,
295
+ metadata=metadata,
296
+ created_at=current_time,
297
+ updated_at=current_time,
159
298
  )
160
299
 
161
- return {
162
- "data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
163
- }
300
+ if isinstance(db, AsyncBaseDb):
301
+ db = cast(AsyncBaseDb, db)
302
+ created_session = await db.upsert_session(session, deserialize=True)
303
+ else:
304
+ created_session = db.upsert_session(session, deserialize=True)
305
+
306
+ if not created_session:
307
+ raise Exception("Failed to create session")
164
308
 
165
- @mcp.tool(name="get_sessions_for_workflow", description="Get list of sessions for a workflow", tags={"session"}) # type: ignore
166
- async def get_sessions_for_workflow(
167
- workflow_id: str,
309
+ if session_type_enum == SessionType.AGENT:
310
+ return AgentSessionDetailSchema.from_session(created_session).model_dump() # type: ignore
311
+ elif session_type_enum == SessionType.TEAM:
312
+ return TeamSessionDetailSchema.from_session(created_session).model_dump() # type: ignore
313
+ else:
314
+ return WorkflowSessionDetailSchema.from_session(created_session).model_dump() # type: ignore
315
+
316
+ @mcp.tool(
317
+ name="get_session_runs",
318
+ description="Get all runs for a specific session",
319
+ tags={"session"},
320
+ ) # type: ignore
321
+ async def get_session_runs(
322
+ session_id: str,
168
323
  db_id: str,
324
+ session_type: str = "agent",
169
325
  user_id: Optional[str] = None,
170
- sort_by: str = "created_at",
171
- sort_order: str = "desc",
172
- ):
326
+ ) -> List[Dict[str, Any]]:
173
327
  db = await get_db(os.dbs, db_id)
328
+ session_type_enum = SessionType(session_type)
329
+
330
+ if isinstance(db, RemoteDb):
331
+ result = await db.get_session_runs(
332
+ session_id=session_id,
333
+ session_type=session_type_enum,
334
+ user_id=user_id,
335
+ db_id=db_id,
336
+ )
337
+ return [r.model_dump() for r in result]
338
+
174
339
  if isinstance(db, AsyncBaseDb):
175
340
  db = cast(AsyncBaseDb, db)
176
- sessions = await db.get_sessions(
177
- session_type=SessionType.WORKFLOW,
178
- component_id=workflow_id,
341
+ session = await db.get_session(
342
+ session_id=session_id, session_type=session_type_enum, user_id=user_id, deserialize=False
343
+ )
344
+ else:
345
+ session = db.get_session(
346
+ session_id=session_id, session_type=session_type_enum, user_id=user_id, deserialize=False
347
+ )
348
+
349
+ if not session:
350
+ raise Exception(f"Session {session_id} not found")
351
+
352
+ runs = session.get("runs") # type: ignore
353
+ if not runs:
354
+ return []
355
+
356
+ run_responses: List[Dict[str, Any]] = []
357
+ for run in runs:
358
+ if session_type_enum == SessionType.AGENT:
359
+ run_responses.append(RunSchema.from_dict(run).model_dump())
360
+ elif session_type_enum == SessionType.TEAM:
361
+ if run.get("agent_id") is not None:
362
+ run_responses.append(RunSchema.from_dict(run).model_dump())
363
+ else:
364
+ run_responses.append(TeamRunSchema.from_dict(run).model_dump())
365
+ else:
366
+ if run.get("workflow_id") is not None:
367
+ run_responses.append(WorkflowRunSchema.from_dict(run).model_dump())
368
+ elif run.get("team_id") is not None:
369
+ run_responses.append(TeamRunSchema.from_dict(run).model_dump())
370
+ else:
371
+ run_responses.append(RunSchema.from_dict(run).model_dump())
372
+
373
+ return run_responses
374
+
375
+ @mcp.tool(
376
+ name="get_session_run",
377
+ description="Get a specific run from a session",
378
+ tags={"session"},
379
+ ) # type: ignore
380
+ async def get_session_run(
381
+ session_id: str,
382
+ run_id: str,
383
+ db_id: str,
384
+ session_type: str = "agent",
385
+ user_id: Optional[str] = None,
386
+ ) -> Dict[str, Any]:
387
+ db = await get_db(os.dbs, db_id)
388
+ session_type_enum = SessionType(session_type)
389
+
390
+ if isinstance(db, RemoteDb):
391
+ result = await db.get_session_run(
392
+ session_id=session_id,
393
+ run_id=run_id,
394
+ session_type=session_type_enum,
179
395
  user_id=user_id,
180
- sort_by=sort_by,
181
- sort_order=sort_order,
182
- deserialize=False,
396
+ db_id=db_id,
397
+ )
398
+ return result.model_dump()
399
+
400
+ if isinstance(db, AsyncBaseDb):
401
+ db = cast(AsyncBaseDb, db)
402
+ session = await db.get_session(
403
+ session_id=session_id, session_type=session_type_enum, user_id=user_id, deserialize=False
404
+ )
405
+ else:
406
+ session = db.get_session(
407
+ session_id=session_id, session_type=session_type_enum, user_id=user_id, deserialize=False
408
+ )
409
+
410
+ if not session:
411
+ raise Exception(f"Session {session_id} not found")
412
+
413
+ runs = session.get("runs") # type: ignore
414
+ if not runs:
415
+ raise Exception(f"Session {session_id} has no runs")
416
+
417
+ target_run = None
418
+ for run in runs:
419
+ if run.get("run_id") == run_id:
420
+ target_run = run
421
+ break
422
+
423
+ if not target_run:
424
+ raise Exception(f"Run {run_id} not found in session {session_id}")
425
+
426
+ if target_run.get("workflow_id") is not None:
427
+ return WorkflowRunSchema.from_dict(target_run).model_dump()
428
+ elif target_run.get("team_id") is not None:
429
+ return TeamRunSchema.from_dict(target_run).model_dump()
430
+ else:
431
+ return RunSchema.from_dict(target_run).model_dump()
432
+
433
+ @mcp.tool(
434
+ name="rename_session",
435
+ description="Rename an existing session",
436
+ tags={"session"},
437
+ ) # type: ignore
438
+ async def rename_session(
439
+ session_id: str,
440
+ session_name: str,
441
+ db_id: str,
442
+ session_type: str = "agent",
443
+ ) -> Dict[str, Any]:
444
+ db = await get_db(os.dbs, db_id)
445
+ session_type_enum = SessionType(session_type)
446
+
447
+ if isinstance(db, RemoteDb):
448
+ result = await db.rename_session(
449
+ session_id=session_id,
450
+ session_name=session_name,
451
+ session_type=session_type_enum,
452
+ db_id=db_id,
183
453
  )
454
+ return result.model_dump()
455
+
456
+ if isinstance(db, AsyncBaseDb):
457
+ db = cast(AsyncBaseDb, db)
458
+ session = await db.rename_session(
459
+ session_id=session_id, session_type=session_type_enum, session_name=session_name
460
+ )
461
+ else:
462
+ db = cast(BaseDb, db)
463
+ session = db.rename_session(
464
+ session_id=session_id, session_type=session_type_enum, session_name=session_name
465
+ )
466
+
467
+ if not session:
468
+ raise Exception(f"Session {session_id} not found")
469
+
470
+ if session_type_enum == SessionType.AGENT:
471
+ return AgentSessionDetailSchema.from_session(session).model_dump() # type: ignore
472
+ elif session_type_enum == SessionType.TEAM:
473
+ return TeamSessionDetailSchema.from_session(session).model_dump() # type: ignore
184
474
  else:
185
- sessions = db.get_sessions(
186
- session_type=SessionType.WORKFLOW,
187
- component_id=workflow_id,
475
+ return WorkflowSessionDetailSchema.from_session(session).model_dump() # type: ignore
476
+
477
+ @mcp.tool(
478
+ name="update_session",
479
+ description="Update session properties like name, state, metadata, or summary",
480
+ tags={"session"},
481
+ ) # type: ignore
482
+ async def update_session(
483
+ session_id: str,
484
+ db_id: str,
485
+ session_type: str = "agent",
486
+ session_name: Optional[str] = None,
487
+ session_state: Optional[Dict[str, Any]] = None,
488
+ metadata: Optional[Dict[str, Any]] = None,
489
+ summary: Optional[Dict[str, Any]] = None,
490
+ user_id: Optional[str] = None,
491
+ ) -> Dict[str, Any]:
492
+ db = await get_db(os.dbs, db_id)
493
+ session_type_enum = SessionType(session_type)
494
+
495
+ if isinstance(db, RemoteDb):
496
+ result = await db.update_session(
497
+ session_id=session_id,
498
+ session_type=session_type_enum,
499
+ session_name=session_name,
500
+ session_state=session_state,
501
+ metadata=metadata,
502
+ summary=summary,
188
503
  user_id=user_id,
189
- sort_by=sort_by,
190
- sort_order=sort_order,
191
- deserialize=False,
504
+ db_id=db_id,
192
505
  )
506
+ return result.model_dump()
193
507
 
194
- return {
195
- "data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
196
- }
508
+ # Get the existing session
509
+ if isinstance(db, AsyncBaseDb):
510
+ db = cast(AsyncBaseDb, db)
511
+ existing_session = await db.get_session(
512
+ session_id=session_id, session_type=session_type_enum, user_id=user_id, deserialize=True
513
+ )
514
+ else:
515
+ existing_session = db.get_session(
516
+ session_id=session_id, session_type=session_type_enum, user_id=user_id, deserialize=True
517
+ )
518
+
519
+ if not existing_session:
520
+ raise Exception(f"Session {session_id} not found")
521
+
522
+ # Update session properties
523
+ if session_name is not None:
524
+ if existing_session.session_data is None: # type: ignore
525
+ existing_session.session_data = {} # type: ignore
526
+ existing_session.session_data["session_name"] = session_name # type: ignore
527
+
528
+ if session_state is not None:
529
+ if existing_session.session_data is None: # type: ignore
530
+ existing_session.session_data = {} # type: ignore
531
+ existing_session.session_data["session_state"] = session_state # type: ignore
532
+
533
+ if metadata is not None:
534
+ existing_session.metadata = metadata # type: ignore
535
+
536
+ if summary is not None:
537
+ from agno.session.summary import SessionSummary
538
+
539
+ existing_session.summary = SessionSummary.from_dict(summary) # type: ignore
540
+
541
+ # Upsert the updated session
542
+ if isinstance(db, AsyncBaseDb):
543
+ updated_session = await db.upsert_session(existing_session, deserialize=True) # type: ignore
544
+ else:
545
+ updated_session = db.upsert_session(existing_session, deserialize=True) # type: ignore
546
+
547
+ if not updated_session:
548
+ raise Exception("Failed to update session")
549
+
550
+ if session_type_enum == SessionType.AGENT:
551
+ return AgentSessionDetailSchema.from_session(updated_session).model_dump() # type: ignore
552
+ elif session_type_enum == SessionType.TEAM:
553
+ return TeamSessionDetailSchema.from_session(updated_session).model_dump() # type: ignore
554
+ else:
555
+ return WorkflowSessionDetailSchema.from_session(updated_session).model_dump() # type: ignore
556
+
557
+ @mcp.tool(
558
+ name="delete_session",
559
+ description="Delete a specific session and all its runs",
560
+ tags={"session"},
561
+ ) # type: ignore
562
+ async def delete_session(
563
+ session_id: str,
564
+ db_id: str,
565
+ ) -> str:
566
+ db = await get_db(os.dbs, db_id)
567
+
568
+ if isinstance(db, RemoteDb):
569
+ await db.delete_session(session_id=session_id, db_id=db_id)
570
+ return "Session deleted successfully"
571
+
572
+ if isinstance(db, AsyncBaseDb):
573
+ db = cast(AsyncBaseDb, db)
574
+ await db.delete_session(session_id=session_id)
575
+ else:
576
+ db = cast(BaseDb, db)
577
+ db.delete_session(session_id=session_id)
578
+
579
+ return "Session deleted successfully"
580
+
581
+ @mcp.tool(
582
+ name="delete_sessions",
583
+ description="Delete multiple sessions by their IDs",
584
+ tags={"session"},
585
+ ) # type: ignore
586
+ async def delete_sessions(
587
+ session_ids: List[str],
588
+ db_id: str,
589
+ session_types: Optional[List[str]] = None,
590
+ ) -> str:
591
+ db = await get_db(os.dbs, db_id)
592
+
593
+ if isinstance(db, RemoteDb):
594
+ # Convert session_types strings to SessionType enums
595
+ session_type_enums = [SessionType(st) for st in session_types] if session_types else []
596
+ await db.delete_sessions(session_ids=session_ids, session_types=session_type_enums, db_id=db_id)
597
+ return "Sessions deleted successfully"
598
+
599
+ if isinstance(db, AsyncBaseDb):
600
+ db = cast(AsyncBaseDb, db)
601
+ await db.delete_sessions(session_ids=session_ids)
602
+ else:
603
+ db = cast(BaseDb, db)
604
+ db.delete_sessions(session_ids=session_ids)
605
+
606
+ return "Sessions deleted successfully"
607
+
608
+ # ==================== Memory Management Tools ====================
197
609
 
198
- # Memory Management Tools
199
610
  @mcp.tool(name="create_memory", description="Create a new user memory", tags={"memory"}) # type: ignore
200
611
  async def create_memory(
201
612
  db_id: str,
@@ -204,90 +615,251 @@ def get_mcp_server(
204
615
  topics: Optional[List[str]] = None,
205
616
  ) -> UserMemorySchema:
206
617
  db = await get_db(os.dbs, db_id)
207
- user_memory = db.upsert_user_memory(
208
- memory=UserMemory(
209
- memory_id=str(uuid4()),
618
+
619
+ if isinstance(db, RemoteDb):
620
+ return await db.create_memory(
210
621
  memory=memory,
211
622
  topics=topics or [],
212
623
  user_id=user_id,
213
- ),
214
- deserialize=False,
215
- )
624
+ db_id=db_id,
625
+ )
626
+
627
+ if isinstance(db, AsyncBaseDb):
628
+ db = cast(AsyncBaseDb, db)
629
+ user_memory = await db.upsert_user_memory(
630
+ memory=UserMemory(
631
+ memory_id=str(uuid4()),
632
+ memory=memory,
633
+ topics=topics or [],
634
+ user_id=user_id,
635
+ ),
636
+ deserialize=False,
637
+ )
638
+ else:
639
+ db = cast(BaseDb, db)
640
+ user_memory = db.upsert_user_memory(
641
+ memory=UserMemory(
642
+ memory_id=str(uuid4()),
643
+ memory=memory,
644
+ topics=topics or [],
645
+ user_id=user_id,
646
+ ),
647
+ deserialize=False,
648
+ )
649
+
216
650
  if not user_memory:
217
651
  raise Exception("Failed to create memory")
218
652
 
219
653
  return UserMemorySchema.from_dict(user_memory) # type: ignore
220
654
 
221
- @mcp.tool(name="get_memories_for_user", description="Get a list of memories for a user", tags={"memory"}) # type: ignore
222
- async def get_memories_for_user(
223
- user_id: str,
655
+ @mcp.tool(
656
+ name="get_memory",
657
+ description="Get a specific memory by ID",
658
+ tags={"memory"},
659
+ ) # type: ignore
660
+ async def get_memory(
661
+ memory_id: str,
662
+ db_id: str,
663
+ user_id: Optional[str] = None,
664
+ ) -> UserMemorySchema:
665
+ db = await get_db(os.dbs, db_id)
666
+
667
+ if isinstance(db, RemoteDb):
668
+ return await db.get_memory(memory_id=memory_id, user_id=user_id, db_id=db_id)
669
+
670
+ if isinstance(db, AsyncBaseDb):
671
+ db = cast(AsyncBaseDb, db)
672
+ user_memory = await db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
673
+ else:
674
+ db = cast(BaseDb, db)
675
+ user_memory = db.get_user_memory(memory_id=memory_id, user_id=user_id, deserialize=False)
676
+
677
+ if not user_memory:
678
+ raise Exception(f"Memory {memory_id} not found")
679
+
680
+ return UserMemorySchema.from_dict(user_memory) # type: ignore
681
+
682
+ @mcp.tool(
683
+ name="get_memories",
684
+ description="Get a paginated list of memories with optional filtering",
685
+ tags={"memory"},
686
+ ) # type: ignore
687
+ async def get_memories(
688
+ db_id: str,
689
+ user_id: Optional[str] = None,
690
+ agent_id: Optional[str] = None,
691
+ team_id: Optional[str] = None,
692
+ topics: Optional[List[str]] = None,
693
+ search_content: Optional[str] = None,
694
+ limit: int = 20,
695
+ page: int = 1,
224
696
  sort_by: str = "updated_at",
225
697
  sort_order: str = "desc",
226
- db_id: Optional[str] = None,
227
- ):
698
+ ) -> Dict[str, Any]:
228
699
  db = await get_db(os.dbs, db_id)
700
+
701
+ if isinstance(db, RemoteDb):
702
+ result = await db.get_memories(
703
+ user_id=user_id or "",
704
+ agent_id=agent_id,
705
+ team_id=team_id,
706
+ topics=topics,
707
+ search_content=search_content,
708
+ limit=limit,
709
+ page=page,
710
+ sort_by=sort_by,
711
+ sort_order=sort_order,
712
+ db_id=db_id,
713
+ )
714
+ return result.model_dump()
715
+
229
716
  if isinstance(db, AsyncBaseDb):
230
717
  db = cast(AsyncBaseDb, db)
231
- user_memories = await db.get_user_memories(
718
+ user_memories, total_count = await db.get_user_memories(
719
+ limit=limit,
720
+ page=page,
232
721
  user_id=user_id,
722
+ agent_id=agent_id,
723
+ team_id=team_id,
724
+ topics=topics,
725
+ search_content=search_content,
233
726
  sort_by=sort_by,
234
727
  sort_order=sort_order,
235
728
  deserialize=False,
236
729
  )
237
730
  else:
238
- user_memories = db.get_user_memories(
731
+ db = cast(BaseDb, db)
732
+ user_memories, total_count = db.get_user_memories(
733
+ limit=limit,
734
+ page=page,
239
735
  user_id=user_id,
736
+ agent_id=agent_id,
737
+ team_id=team_id,
738
+ topics=topics,
739
+ search_content=search_content,
240
740
  sort_by=sort_by,
241
741
  sort_order=sort_order,
242
742
  deserialize=False,
243
743
  )
744
+
745
+ memories = [UserMemorySchema.from_dict(m) for m in user_memories] # type: ignore
244
746
  return {
245
- "data": [UserMemorySchema.from_dict(user_memory) for user_memory in user_memories], # type: ignore
747
+ "data": [m.model_dump() for m in memories if m is not None],
748
+ "meta": {
749
+ "page": page,
750
+ "limit": limit,
751
+ "total_count": total_count,
752
+ "total_pages": (total_count + limit - 1) // limit if limit > 0 else 0, # type: ignore
753
+ },
246
754
  }
247
755
 
248
- @mcp.tool(name="update_memory", description="Update a memory", tags={"memory"}) # type: ignore
756
+ @mcp.tool(name="update_memory", description="Update an existing memory", tags={"memory"}) # type: ignore
249
757
  async def update_memory(
250
758
  db_id: str,
251
759
  memory_id: str,
252
760
  memory: str,
253
761
  user_id: str,
762
+ topics: Optional[List[str]] = None,
254
763
  ) -> UserMemorySchema:
255
764
  db = await get_db(os.dbs, db_id)
765
+
766
+ if isinstance(db, RemoteDb):
767
+ return await db.update_memory(
768
+ memory_id=memory_id,
769
+ memory=memory,
770
+ topics=topics or [],
771
+ user_id=user_id,
772
+ db_id=db_id,
773
+ )
774
+
256
775
  if isinstance(db, AsyncBaseDb):
257
776
  db = cast(AsyncBaseDb, db)
258
777
  user_memory = await db.upsert_user_memory(
259
778
  memory=UserMemory(
260
779
  memory_id=memory_id,
261
780
  memory=memory,
781
+ topics=topics or [],
262
782
  user_id=user_id,
263
783
  ),
264
784
  deserialize=False,
265
785
  )
266
786
  else:
787
+ db = cast(BaseDb, db)
267
788
  user_memory = db.upsert_user_memory(
268
789
  memory=UserMemory(
269
790
  memory_id=memory_id,
270
791
  memory=memory,
792
+ topics=topics or [],
271
793
  user_id=user_id,
272
794
  ),
273
795
  deserialize=False,
274
796
  )
797
+
275
798
  if not user_memory:
276
799
  raise Exception("Failed to update memory")
277
800
 
278
801
  return UserMemorySchema.from_dict(user_memory) # type: ignore
279
802
 
280
- @mcp.tool(name="delete_memory", description="Delete a memory by ID", tags={"memory"}) # type: ignore
803
+ @mcp.tool(name="delete_memory", description="Delete a specific memory by ID", tags={"memory"}) # type: ignore
281
804
  async def delete_memory(
282
805
  db_id: str,
283
806
  memory_id: str,
284
- ) -> None:
807
+ user_id: Optional[str] = None,
808
+ ) -> str:
285
809
  db = await get_db(os.dbs, db_id)
810
+
811
+ if isinstance(db, RemoteDb):
812
+ await db.delete_memory(memory_id=memory_id, user_id=user_id, db_id=db_id)
813
+ return "Memory deleted successfully"
814
+
286
815
  if isinstance(db, AsyncBaseDb):
287
816
  db = cast(AsyncBaseDb, db)
288
- await db.delete_user_memory(memory_id=memory_id)
817
+ await db.delete_user_memory(memory_id=memory_id, user_id=user_id)
289
818
  else:
290
- db.delete_user_memory(memory_id=memory_id)
819
+ db = cast(BaseDb, db)
820
+ db.delete_user_memory(memory_id=memory_id, user_id=user_id)
291
821
 
822
+ return "Memory deleted successfully"
823
+
824
+ @mcp.tool(
825
+ name="delete_memories",
826
+ description="Delete multiple memories by their IDs",
827
+ tags={"memory"},
828
+ ) # type: ignore
829
+ async def delete_memories(
830
+ memory_ids: List[str],
831
+ db_id: str,
832
+ user_id: Optional[str] = None,
833
+ ) -> str:
834
+ db = await get_db(os.dbs, db_id)
835
+
836
+ if isinstance(db, RemoteDb):
837
+ await db.delete_memories(memory_ids=memory_ids, user_id=user_id, db_id=db_id)
838
+ return "Memories deleted successfully"
839
+
840
+ if isinstance(db, AsyncBaseDb):
841
+ db = cast(AsyncBaseDb, db)
842
+ await db.delete_user_memories(memory_ids=memory_ids, user_id=user_id)
843
+ else:
844
+ db = cast(BaseDb, db)
845
+ db.delete_user_memories(memory_ids=memory_ids, user_id=user_id)
846
+
847
+ return "Memories deleted successfully"
848
+
849
+ # Use http_app for Streamable HTTP transport (modern MCP standard)
292
850
  mcp_app = mcp.http_app(path="/mcp")
851
+
852
+ # Add JWT middleware to MCP app if authorization is enabled
853
+ if os.authorization and os.authorization_config:
854
+ from agno.os.middleware.jwt import JWTMiddleware
855
+
856
+ mcp_app.add_middleware(
857
+ JWTMiddleware,
858
+ verification_keys=os.authorization_config.verification_keys,
859
+ jwks_file=os.authorization_config.jwks_file,
860
+ algorithm=os.authorization_config.algorithm or "RS256",
861
+ authorization=os.authorization,
862
+ verify_audience=os.authorization_config.verify_audience or False,
863
+ )
864
+
293
865
  return mcp_app