From e77417b6cd439fedcf8654512106efb78829a63b Mon Sep 17 00:00:00 2001 From: daniel Date: Sat, 16 May 2026 09:25:55 -0500 Subject: [PATCH] feat: wire factcloud as direct MCP connection, drop knowledge_agent subagent - Rename FACTBASE_CLOUD_* -> FACTCLOUD_* in config.py + SSM paths - factcloud MCPClient added directly to main agent tool set - knowledge_agent subagent removed (SSM + TOOL_PRESETS) - System prompt updated: factcloud tools are direct, not via subagent --- agentclaw/app/agent_claw_main/config.py | 12 ++++----- agentclaw/app/agent_claw_main/main.py | 35 +++++++++++++++---------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/agentclaw/app/agent_claw_main/config.py b/agentclaw/app/agent_claw_main/config.py index f5325a1..cf2b7cf 100644 --- a/agentclaw/app/agent_claw_main/config.py +++ b/agentclaw/app/agent_claw_main/config.py @@ -6,9 +6,9 @@ _DEFAULTS = { '/agent-claw/model-id': 'us.anthropic.claude-sonnet-4-6', '/agent-claw/config/compaction_model_id': 'us.anthropic.claude-3-5-haiku-20241022-v1:0', '/agent-claw/aws-mcp-url': 'https://aws-mcp.us-east-1.api.aws/mcp', - '/agent-claw/factbase-cloud/client-id': '', - '/agent-claw/factbase-cloud/client-secret': '', - '/agent-claw/factbase-cloud/mcp-url': '', + '/agent-claw/factcloud/client-id': '', + '/agent-claw/factcloud/client-secret': '', + '/agent-claw/factcloud/mcp-url': '', } @@ -28,6 +28,6 @@ _params = _load() AGENT_MODEL_ID: str = _params['/agent-claw/model-id'] COMPACTION_MODEL_ID: str = _params['/agent-claw/config/compaction_model_id'] AWS_MCP_URL: str = _params['/agent-claw/aws-mcp-url'] -FACTBASE_CLOUD_CLIENT_ID: str = _params.get('/agent-claw/factbase-cloud/client-id', '') -FACTBASE_CLOUD_CLIENT_SECRET: str = _params.get('/agent-claw/factbase-cloud/client-secret', '') -FACTBASE_CLOUD_MCP_URL: str = _params.get('/agent-claw/factbase-cloud/mcp-url', '') +FACTCLOUD_CLIENT_ID: str = _params.get('/agent-claw/factcloud/client-id', '') +FACTCLOUD_CLIENT_SECRET: str = _params.get('/agent-claw/factcloud/client-secret', '') +FACTCLOUD_MCP_URL: str = _params.get('/agent-claw/factcloud/mcp-url', '') diff --git a/agentclaw/app/agent_claw_main/main.py b/agentclaw/app/agent_claw_main/main.py index b7687c0..9f45b72 100644 --- a/agentclaw/app/agent_claw_main/main.py +++ b/agentclaw/app/agent_claw_main/main.py @@ -89,23 +89,23 @@ except Exception as _e: print(traceback.format_exc()) -# ── factbase-cloud token cache ──────────────────────────────────────────── +# ── factcloud token cache ──────────────────────────────────────────────── _fc_token: str = '' _fc_token_expiry: float = 0.0 -def _get_factbase_cloud_token() -> str: - """Fetch/cache a Cognito M2M token for factbase-cloud. Thread-safe for Lambda.""" +def _get_factcloud_token() -> str: + """Fetch/cache a Cognito M2M token for factcloud. Thread-safe for Lambda.""" global _fc_token, _fc_token_expiry if _fc_token and time.time() < _fc_token_expiry - 60: return _fc_token - if not config.FACTBASE_CLOUD_CLIENT_ID or not config.FACTBASE_CLOUD_CLIENT_SECRET: - raise RuntimeError('factbase-cloud credentials not configured in SSM') + if not config.FACTCLOUD_CLIENT_ID or not config.FACTCLOUD_CLIENT_SECRET: + raise RuntimeError('factcloud credentials not configured in SSM') resp = httpx.post( 'https://factbase-cloud.auth.us-east-1.amazoncognito.com/oauth2/token', data={ 'grant_type': 'client_credentials', - 'client_id': config.FACTBASE_CLOUD_CLIENT_ID, - 'client_secret': config.FACTBASE_CLOUD_CLIENT_SECRET, + 'client_id': config.FACTCLOUD_CLIENT_ID, + 'client_secret': config.FACTCLOUD_CLIENT_SECRET, 'scope': 'factbase-cloud/read factbase-cloud/write', }, headers={'Content-Type': 'application/x-www-form-urlencoded'}, @@ -115,7 +115,7 @@ def _get_factbase_cloud_token() -> str: data = resp.json() _fc_token = data['access_token'] _fc_token_expiry = time.time() + data.get('expires_in', 3600) - print(f'[main] factbase-cloud token refreshed, expires in {data.get("expires_in", 3600)}s') + print(f'[main] factcloud token refreshed, expires in {data.get("expires_in", 3600)}s') return _fc_token @@ -127,10 +127,6 @@ TOOL_PRESETS = { "aws": lambda: [MCPClient(lambda: aws_iam_streamablehttp_client(config.AWS_MCP_URL, aws_service="aws-mcp"))], "coding": lambda: [MCPClient(lambda: aws_iam_streamablehttp_client(config.AWS_MCP_URL, aws_service="aws-mcp")), run_code], "documents": lambda: [http_request, file_read], - "factbase": lambda: [MCPClient(lambda: streamablehttp_client( - url=config.FACTBASE_CLOUD_MCP_URL, - headers={"Authorization": f"Bearer {_get_factbase_cloud_token()}"}, - ))], } @@ -690,7 +686,7 @@ async def main(payload: dict, context): system_prompt = system_prompt + '\n\n---\n\n' + ltm_block system_prompt += '\nAWS tools available: call_aws (any AWS API via AWS MCP Server), aws_list_lambda_functions, aws_get_cost_and_usage, aws_describe_service. Use call_aws directly for AWS API calls — do NOT say you lack AWS access.' - system_prompt += '\n\nSubagents available — use them aggressively to save cost and improve quality:\n- aws_agent: all AWS infrastructure, cost, resource, IAM, CloudWatch queries\n- coding_agent: code writing, builds, deployments, CodeBuild/AppRunner/ECR\n- document_agent: summarize URLs, extract data from documents, process long text\n- knowledge_agent: query factbase-cloud (your personal knowledge graph) — search facts, retrieve stored knowledge, run factbase workflows. Use this whenever asked about factbase, factcloud, or personal knowledge base.\nDefault to delegating; only answer directly for simple conversational responses or tasks that don\'t fit a subagent.' + system_prompt += '\n\nSubagents available — use them aggressively to save cost and improve quality:\n- aws_agent: all AWS infrastructure, cost, resource, IAM, CloudWatch queries\n- coding_agent: code writing, builds, deployments, CodeBuild/AppRunner/ECR\n- document_agent: summarize URLs, extract data from documents, process long text\nYou also have direct access to factcloud MCP tools (your personal knowledge graph) — use them directly for any factbase, factcloud, or knowledge base queries. Do NOT say you lack access to factcloud.\nDefault to delegating to subagents; only answer directly for simple conversational responses or tasks that don\'t fit a subagent.' # Model: claude-sonnet-4-6 via cross-region inference # NOTE: extended thinking disabled — causes retry/duplicate issues with streaming @@ -708,6 +704,17 @@ async def main(payload: dict, context): run_code, send_file, request_iam_permission, apply_iam_permission, aws_list_lambda_functions, aws_get_cost_and_usage, aws_describe_service] + # factcloud: direct MCP connection (M2M Cognito auth) + factcloud_clients = [] + if config.FACTCLOUD_MCP_URL: + try: + factcloud_clients = [MCPClient(lambda: streamablehttp_client( + url=config.FACTCLOUD_MCP_URL, + headers={"Authorization": f"Bearer {_get_factcloud_token()}"}, + ))] + except Exception as e: + print(f'[main] factcloud connection failed: {e}') + # Load user's dynamic MCP connections mcp_connections = services.get('mcp_connections', []) mcp_clients, _mcp_to_close = mcp_loader.load_mcp_tools(mcp_connections, actor_id) @@ -716,7 +723,7 @@ async def main(payload: dict, context): ssm = boto3.client('ssm', region_name='us-east-1') subagent_tools = _load_subagents(ssm) - all_tools = base_tools + _aws_mcp_tools + mcp_clients + subagent_tools + all_tools = base_tools + factcloud_clients + _aws_mcp_tools + mcp_clients + subagent_tools agent = Agent( model=model,