mirror of
https://github.com/hoshikawa2/first_contas.git
synced 2026-07-10 02:34:20 +00:00
first commit
This commit is contained in:
BIN
mcp_servers/.DS_Store
vendored
Normal file
BIN
mcp_servers/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
mcp_servers/legacy_tim_mcp/.DS_Store
vendored
Normal file
BIN
mcp_servers/legacy_tim_mcp/.DS_Store
vendored
Normal file
Binary file not shown.
308
mcp_servers/legacy_tim_mcp/.env.example
Normal file
308
mcp_servers/legacy_tim_mcp/.env.example
Normal file
@@ -0,0 +1,308 @@
|
||||
APP_NAME=agent-contas-first-migrated
|
||||
APP_ENV=local
|
||||
LOG_LEVEL=DEBUG
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
|
||||
# =====================================================================
|
||||
# Agent Framework OCI - LLM
|
||||
# Mapeado a partir das variáveis TIM_LLM_* do legado.
|
||||
# =====================================================================
|
||||
LLM_PROVIDER=oci
|
||||
LLM_TEMPERATURE=0.0
|
||||
LLM_MAX_TOKENS=10000
|
||||
LLM_TIMEOUT_SECONDS=0
|
||||
OCI_GENAI_BASE_URL=https://pegruagntaiatenddev.pe.inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
|
||||
OCI_GENAI_MODEL=ocid1.generativeaiendpoint.oc1.sa-saopaulo-1.amaaaaaaaehl73aa2amkvyvpsv3ts6rdbx6tzya43zgwx7vqljtjade2vjya
|
||||
OCI_GENAI_API_KEY=
|
||||
OCI_CONFIG_FILE=/Users/cristianohoshikawa/Dropbox/ORACLE/TIM/FIRST/config/config
|
||||
OCI_PROFILE=DEFAULT
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaa3prjvf7mkvijwn5ng5h6n5ftanbbwqfg6cu44kmffwamhyy267iq
|
||||
OCI_REGION=sa-saopaulo-1
|
||||
|
||||
# Variáveis OCI legadas mantidas para compatibilidade com adapters/scripts.
|
||||
TIM_LLM_PROVIDER=oci
|
||||
TIM_LLM_MODEL=120b
|
||||
TIM_LLM_API_KEY=
|
||||
TIM_LLM_BASE_URL=
|
||||
TIM_LLM_API_VERSION=
|
||||
TIM_LLM_TEMPERATURE=0.0
|
||||
TIM_LLM_MAX_TOKENS=10000
|
||||
TIM_LLM_REQUEST_TIMEOUT=0
|
||||
TIM_LLM_EXTRA={"auth_type":"API_KEY","provider":"meta"}
|
||||
TIM_LLM_OCI_VARIANT=120b
|
||||
TIM_LLM_OCI_MODEL_ID_20B=ocid1.generativeaiendpoint.oc1.sa-saopaulo-1.amaaaaaaaehl73aarckk4norgg263as5zleyp36tujnynrssoohshf3nwlhq
|
||||
TIM_LLM_OCI_MODEL_ID_120B=ocid1.generativeaiendpoint.oc1.sa-saopaulo-1.amaaaaaaaehl73aa2amkvyvpsv3ts6rdbx6tzya43zgwx7vqljtjade2vjya
|
||||
TIM_LLM_OCI_SERVICE_ENDPOINT=https://pegruagntaiatenddev.pe.inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
|
||||
TIM_LLM_OCI_MODEL_KWARGS={"temperature":0.1,"top_p":0.1,"reasoning_effort":"LOW","max_tokens":5000}
|
||||
TIM_LLM_OCI_MODEL_KWARGS_20B={"temperature":0.0,"top_p":0.1,"reasoning_effort":"LOW","max_tokens":5000}
|
||||
TIM_LLM_OCI_MODEL_KWARGS_120B={"temperature":0.0,"top_p":0.1,"reasoning_effort":"LOW","max_tokens":5000}
|
||||
TIM_LLM_OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaa3prjvf7mkvijwn5ng5h6n5ftanbbwqfg6cu44kmffwamhyy267iq
|
||||
TIM_OCI_USER=ocid1.user.oc1..aaaaaaaa2m2rrqp2m272oa6kfulx3kouaijutu4iik4xhtttg6l76nrp4rza
|
||||
TIM_OCI_FINGERPRINT=89:b7:68:10:11:ff:60:45:e1:4c:4b:f2:68:3e:e5:3f
|
||||
TIM_OCI_TENANCY=ocid1.tenancy.oc1..aaaaaaaayzepbi32gafno3hj23o3d6tuquqetuw3yelxb5y7t2ft2wtm2i7q
|
||||
TIM_OCI_REGION=sa-saopaulo-1
|
||||
TIM_OCI_KEY_FILE=/Users/cristianohoshikawa/Dropbox/ORACLE/TIM/FIRST/config/sa-agnt-ai-atendimento-contas-dev-2026-03-20T14_02_49.145Z.pem
|
||||
TIM_LLM_OCI_AUTH_FILE_LOCATION=/Users/cristianohoshikawa/Dropbox/ORACLE/TIM/FIRST/config/config
|
||||
|
||||
# =====================================================================
|
||||
# Repositórios / persistência do framework
|
||||
# =====================================================================
|
||||
SESSION_REPOSITORY_PROVIDER=memory
|
||||
MEMORY_REPOSITORY_PROVIDER=memory
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=memory
|
||||
USAGE_REPOSITORY_PROVIDER=memory
|
||||
TIM_STATE_BACKEND=memory
|
||||
|
||||
# Autonomous DB / Oracle Store
|
||||
ADB_USER=USR_ADB_AGNTATEND_W_DEV
|
||||
ADB_PASSWORD="T!M#Esta026!"
|
||||
ADB_DSN="(description=(retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1522)(host=10.152.100.72))(connect_data=(service_name=gf9a4a2e79cfeb2_agntatendimentodev_low.adb.oraclecloud.com))(security=(ssl_server_dn_match=no)))"
|
||||
ADB_WALLET_LOCATION=
|
||||
ADB_WALLET_PASSWORD=
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
|
||||
TIM_DB_TNS_NAME=agntatendimentodev_low
|
||||
TIM_DB_CONNECT_STRING="(description= (retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1522)(host=cxprbmua.adb.sa-saopaulo-1.oraclecloud.com))(connect_data=(service_name=gf9a4a2e79cfeb2_agntatendimentodev_low.adb.oraclecloud.com))(security=(ssl_server_dn_match=no)))"
|
||||
TIM_DB_USER=
|
||||
TIM_DB_PASSWORD=
|
||||
TIM_DB_WALLET_PASSWORD=
|
||||
TIM_DB_USE_WALLET=true
|
||||
|
||||
# =====================================================================
|
||||
# RAG / Vector Store do framework
|
||||
# =====================================================================
|
||||
VECTOR_STORE_PROVIDER=autonomous
|
||||
GRAPH_STORE_PROVIDER=memory
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=oci
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json,*.pdf
|
||||
TIM_RAG_ENABLED=true
|
||||
TIM_RAG_OCI_SERVICE_ENDPOINT=https://inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
|
||||
TIM_RAG_DB_USER=USR_ADB_AGNTATEND_W_DEV
|
||||
TIM_RAG_DB_PASSWORD="T!M#Esta026!"
|
||||
TIM_RAG_DB_DSN="(description=(retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1522)(host=10.152.100.72))(connect_data=(service_name=gf9a4a2e79cfeb2_agntatendimentodev_low.adb.oraclecloud.com))(security=(ssl_server_dn_match=no)))"
|
||||
|
||||
# =====================================================================
|
||||
# Langfuse / observabilidade
|
||||
# =====================================================================
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_PUBLIC_KEY=
|
||||
LANGFUSE_SECRET_KEY=
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
TIM_LLM_GATEWAY_LANGFUSE_PUBLIC_KEY="pk-lf-ec665df6-a762-4c10-9412-7cb7035a7a19"
|
||||
TIM_LLM_GATEWAY_LANGFUSE_SECRET_KEY="sk-lf-e90bef95-b166-4ee4-8a23-dfc1ac169a17"
|
||||
TIM_LLM_GATEWAY_LANGFUSE_HOST=http://localhost:3005
|
||||
TIM_LLM_GATEWAY_LANGFUSE_DEFAULT_LABEL=production
|
||||
TIM_LLM_GATEWAY_LANGFUSE_CACHE_TTL_SECONDS=60
|
||||
TIM_LLM_GATEWAY_LANGFUSE_MAX_RETRIES=3
|
||||
TIM_LLM_GATEWAY_LANGFUSE_FETCH_TIMEOUT_SECONDS=5
|
||||
TIM_LLM_GATEWAY_LANGFUSE_MASK_SENSITIVE_DATA=true
|
||||
TIM_AGENT_FRAMEWORK_LOG_EXPORT_ENABLED=false
|
||||
|
||||
ENABLE_ANALYTICS=false
|
||||
ANALYTICS_PROVIDERS=noop
|
||||
ENABLE_OCI_STREAMING=false
|
||||
|
||||
# =====================================================================
|
||||
# Guardrails / Supervisor / Judges do framework
|
||||
# =====================================================================
|
||||
ENABLE_INPUT_GUARDRAILS=true
|
||||
ENABLE_OUTPUT_GUARDRAILS=true
|
||||
ENABLE_JUDGES=true
|
||||
ENABLE_SUPERVISOR=true
|
||||
ENABLE_OUTPUT_SUPERVISOR=true
|
||||
ENABLE_PARALLEL_GUARDRAILS=true
|
||||
GUARDRAILS_FAIL_FAST=true
|
||||
OUTPUT_SUPERVISOR_MAX_RETRIES=3
|
||||
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
|
||||
JUDGES_CONFIG_PATH=./config/judges.yaml
|
||||
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
|
||||
USE_MOCK_LLM=false
|
||||
GUARDRAIL_LLM=20b
|
||||
|
||||
# =====================================================================
|
||||
# Channel / Routing / MCP framework
|
||||
# =====================================================================
|
||||
DEFAULT_CHANNEL=web
|
||||
FRAMEWORK_CHANNEL_INPUT_MODE=embedded
|
||||
ENABLE_VOICE_ADAPTER=true
|
||||
ENABLE_WHATSAPP_ADAPTER=true
|
||||
ENABLE_TEXT_ADAPTER=true
|
||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
ENABLE_LLM_ROUTER=false
|
||||
ROUTING_MODE=router
|
||||
|
||||
ENABLE_MCP_TOOLS=true
|
||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||
|
||||
ENABLE_REDIS_CACHE=false
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
||||
MEMORY_CONTEXT_STRATEGY=summary
|
||||
MEMORY_HISTORY_LIMIT=80
|
||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
||||
MEMORY_SUMMARY_USE_LLM=false
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
|
||||
# =====================================================================
|
||||
# MCP legacy TIM/FIRST - integração real por padrão
|
||||
# =====================================================================
|
||||
TIM_MCP_USE_MOCK=false
|
||||
TIM_MCP_TIMEOUT_SECONDS=30
|
||||
TIM_GATEWAY_RETRY_MAX_RETRIES=3
|
||||
TIM_GATEWAY_RETRY_BACKOFF_FACTOR=0.5
|
||||
TIM_GATEWAY_DEFAULT_TIMEOUT=30
|
||||
TIM_DEFAULT_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_CLIENT_ID=AIAGENTCR
|
||||
TIM_USER_ID=AIAGENTCR
|
||||
TIM_DEFAULT_CLIENT_ID=CHAT
|
||||
TIM_DEFAULT_CSP_ID=740
|
||||
TIM_DEFAULT_CHANNEL=APP
|
||||
TIM_DEFAULT_CHANNEL_LEGACY=APP
|
||||
TIM_AUTHORIZATION_OAM=
|
||||
TIM_CN_FIELD=
|
||||
TIM_TYPE_FIELD=
|
||||
|
||||
# Consulta VAS
|
||||
TIM_QUERY_VAS_URL=http://10.151.3.100:8000/access/v1/productsSingleVas
|
||||
TIM_QUERY_VAS_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_QUERY_VAS_TIMEOUT=30
|
||||
TIM_URL_CONSULTA_VAS=http://10.151.3.100:8000/access/v1/productsSingleVas
|
||||
TIM_CONSULTA_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_CONSULTA_TIMEOUT=30
|
||||
|
||||
# Bloqueio VAS
|
||||
TIM_BLOCK_VAS_URL=http://10.151.3.100:8000/customers/v1/createPartialBlocking
|
||||
TIM_BLOCK_VAS_AUTH="Basic YmZmZGlnaXRhbDpiZmZkaWdpdGFsMDE="
|
||||
TIM_BLOCK_VAS_TIMEOUT=30
|
||||
TIM_BLOCK_VAS_CLIENT_ID=AIAGENTCR
|
||||
TIM_BLOCK_VAS_OPERATION_TYPE=block
|
||||
TIM_BLOCK_VAS_PAYLOAD_MODE=auto
|
||||
TIM_BLOCK_VAS_ACCEPT_ENCODING=gzip,deflate
|
||||
TIM_URL_BLOQUEIO_VAS=http://10.151.3.100:8000/customers/v1/createPartialBlocking
|
||||
TIM_BLOQUEIO_AUTH="Basic YmZmZGlnaXRhbDpiZmZkaWdpdGFsMDE="
|
||||
TIM_BLOQUEIO_TIMEOUT=30
|
||||
TIM_BLOQUEIO_OPERATION_TYPE=block
|
||||
TIM_BLOQUEIO_ACCEPT_ENCODING=gzip,deflate
|
||||
|
||||
# Cancelamento VAS
|
||||
TIM_CANCEL_VAS_URL=http://10.151.3.100:8000/access/v1/singleVas
|
||||
TIM_CANCEL_VAS_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_CANCEL_VAS_TIMEOUT=30
|
||||
TIM_CANCEL_VAS_CLIENT_ID=AIAGENTCR
|
||||
TIM_CANCELAMENTO_URL=http://10.151.3.100:8000/access/v1/singleVas
|
||||
TIM_CANCELAMENTO_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_CANCELAMENTO_TIMEOUT=30
|
||||
|
||||
# Contrato
|
||||
TIM_CONTRATO_URL=http://10.151.3.100:8000/access/v1/contractInformation
|
||||
TIM_CONTRATO_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_CONTRATO_TIMEOUT=30
|
||||
|
||||
# Complete invoices / fatura completa
|
||||
TIM_COMPLETE_INVOICES_URL=http://10.151.3.100:8000/customers/v1/completeInvoices
|
||||
TIM_COMPLETE_INVOICES_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_COMPLETE_INVOICES_TIMEOUT=30
|
||||
TIM_COMPLETE_INVOICES_CLIENT_ID=AIAGENTCR
|
||||
|
||||
# Perfil de fatura
|
||||
TIM_PROFILE_BILL_URL=http://10.151.3.100:8000/customers/v1/completeInvoices
|
||||
TIM_PROFILE_BILL_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_PROFILE_BILL_TIMEOUT=30
|
||||
TIM_URL_PERFIL_FATURA=http://10.151.3.100:8000/customers/v1/completeInvoices
|
||||
TIM_PROFILE_FULL_URL=http://server:port/v1/r-profile-full
|
||||
|
||||
# Divergência / billing analysis
|
||||
TIM_DIVERGENCIA_URL=http://10.151.3.100:8000/customers/v1/billingAnalysis
|
||||
TIM_DIVERGENCIA_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_INVOICE_EXPLANATION_URL=http://10.151.3.100:8000/customers/v1/billingAnalysis
|
||||
TIM_INVOICE_EXPLANATION_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
|
||||
# Bill/PDF / invoice recover
|
||||
TIM_BILL_PDF_URL=http://10.151.3.100:8000/invoices/v1/invoiceRecover
|
||||
TIM_BILL_PDF_AUTH="Basic YmZmZGlnaXRhbDpiZmZkaWdpdGFsMDE="
|
||||
TIM_BILL_PDF_TIMEOUT=30
|
||||
TIM_BILL_PDF_CLIENT_ID=TIMX
|
||||
TIM_URL_INVOICE_RECOVER=http://10.151.3.100:8000/invoices/v1/invoiceRecover
|
||||
TIM_INVOICE_RECOVER_AUTH="Basic YmZmZGlnaXRhbDpiZmZkaWdpdGFsMDE="
|
||||
TIM_INVOICE_RECOVER_TIMEOUT=30
|
||||
TIM_INVOICE_RECOVER_CLIENT_ID=TIMX
|
||||
|
||||
# Protocolo / backOfficeSRopening
|
||||
TIM_PROTOCOL_URL=http://10.151.3.100:8000/customers/v1/backOfficeSRopening
|
||||
TIM_PROTOCOL_AUTH="Basic YmZmZGlnaXRhbDpiZmZkaWdpdGFsMDE="
|
||||
TIM_PROTOCOL_TIMEOUT=30
|
||||
|
||||
# Contestação/SR
|
||||
TIM_CUSTOMER_CONTESTATION_URL=http://10.151.3.100:8000/interactions/v1/customerContestation
|
||||
TIM_CUSTOMER_CONTESTATION_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_CUSTOMER_CONTESTATION_TIMEOUT=30
|
||||
TIM_CUSTOMER_CONTESTATION_CLIENT_ID=AIAGENTCR
|
||||
TIM_CUSTOMER_CONTESTATION_USER_ID=aiagentcr
|
||||
|
||||
# Status SR
|
||||
TIM_SERVICE_REQUEST_STATUS_URL=http://10.151.3.100:8000/interactions/v1/statusServiceRequest
|
||||
TIM_SERVICE_REQUEST_STATUS_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_SERVICE_REQUEST_STATUS_TIMEOUT=30
|
||||
|
||||
# SMS
|
||||
TIM_SMS_URL=http://10.151.3.100:8000/customers/v1/smsSend
|
||||
TIM_SMS_AUTH="Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsIng1dCI6IjVubEFuRWxwaFlNZ3R4Nm1JbFRMN1VuX2x2RSIsImtpZCI6Im9yYWtleSJ9.eyJzdWIiOiJlY29tbWVyY2VjYyIsImlzcyI6Im9yYWtleTJfb2FtdmFzcWEiLCJvcmFjbGUub2F1dGguc3ZjX3BfbiI6Ik9BdXRoU2VydmljZVByb2ZpbGUiLCJpYXQiOjE3NzQ2MjU3NjIsIm9yYWNsZS5vYXV0aC5wcm4uaWRfdHlwZSI6IkNsaWVudElEIiwiZXhwIjoxNzc0NjQwMTYyLCJvcmFjbGUub2F1dGgudGtfY29udGV4dCI6InJlc291cmNlX2FjY2Vzc190ayIsInBybiI6ImVjb21tZXJjZWNjIiwianRpIjoiZGJkNmUwOTgtOThmYi00YmFjLTkyOGItOTRkZmIwYTIwYWI3Iiwib3JhY2xlLm9hdXRoLmNsaWVudF9vcmlnaW5faWQiOiJlY29tbWVyY2VjYyIsIm9yYWNsZS5vYXV0aC5zY29wZSI6ImVjb21tZXJjZS5jdXN0b21lciIsInVzZXIudGVuYW50Lm5hbWUiOiJEZWZhdWx0RG9tYWluIiwib3JhY2xlLm9hdXRoLmlkX2RfaWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0NTY3ODkwMTIifQ.fJ_zeOegG9UcYFz87NqSf3E-pnV2i_GLiJ19BumC6cyiGJunjBNTa0CxpAx0YTJznwV9l7kcBVh2rmmVrm4JH4db3Q0N_sR9l0qaKfwjFgzjUcwI7dVSDzzCCZHuYfqg1rOAUghQOPk02Ht2kxT5jaJtCYxRnK9FSnupPyzPXHI"
|
||||
TIM_SMS_TIMEOUT=30
|
||||
TIM_SMS_CLIENT_ID=Ecommercecc
|
||||
TIM_SMS_SENDER_ADDRESS=TIM
|
||||
TIM_SMS_SENDER_NAME=TIM
|
||||
TIM_SMS_TOKEN_URL=
|
||||
TIM_SMS_TOKEN_CLIENT_ID=
|
||||
TIM_SMS_TOKEN_CLIENT_SECRET=
|
||||
TIM_SMS_TOKEN_SCOPE=
|
||||
TIM_SMS_TOKEN_AUDIENCE=
|
||||
TIM_SMS_TOKEN_TIMEOUT=
|
||||
SMS_BARCODE_AUTH="Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsIng1dCI6IjVubEFuRWxwaFlNZ3R4Nm1JbFRMN1VuX2x2RSIsImtpZCI6Im9yYWtleSJ9.eyJzdWIiOiJlY29tbWVyY2VjYyIsImlzcyI6Im9yYWtleTJfb2FtdmFzcWEiLCJvcmFjbGUub2F1dGguc3ZjX3BfbiI6Ik9BdXRoU2VydmljZVByb2ZpbGUiLCJpYXQiOjE3NzQ2MjU3NjIsIm9yYWNsZS5vYXV0aC5wcm4uaWRfdHlwZSI6IkNsaWVudElEIiwiZXhwIjoxNzc0NjQwMTYyLCJvcmFjbGUub2F1dGgudGtfY29udGV4dCI6InJlc291cmNlX2FjY2Vzc190ayIsInBybiI6ImVjb21tZXJjZWNjIiwianRpIjoiZGJkNmUwOTgtOThmYi00YmFjLTkyOGItOTRkZmIwYTIwYWI3Iiwib3JhY2xlLm9hdXRoLmNsaWVudF9vcmlnaW5faWQiOiJlY29tbWVyY2VjYyIsIm9yYWNsZS5vYXV0aC5zY29wZSI6ImVjb21tZXJjZS5jdXN0b21lciIsInVzZXIudGVuYW50Lm5hbWUiOiJEZWZhdWx0RG9tYWluIiwib3JhY2xlLm9hdXRoLmlkX2RfaWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0NTY3ODkwMTIifQ.fJ_zeOegG9UcYFz87NqSf3E-pnV2i_GLiJ19BumC6cyiGJunjBNTa0CxpAx0YTJznwV9l7kcBVh2rmmVrm4JH4db3Q0N_sR9l0qaKfwjFgzjUcwI7dVSDzzCCZHuYfqg1rOAUghQOPk02Ht2kxT5jaJtCYxRnK9FSnupPyzPXHI"
|
||||
|
||||
# Tracking activities
|
||||
TIM_TRACKING_ACTIVITIES_URL=http://10.151.3.100:8000/customers/v1/trackingActivities
|
||||
TIM_TRACKING_ACTIVITIES_AUTH="Basic dXJhOnVyYTAwMDAx"
|
||||
TIM_TRACKING_ACTIVITIES_TIMEOUT=30
|
||||
TIM_TRACKING_ACTIVITIES_USER_LOGIN=
|
||||
TIM_TRACKING_ACTIVITIES_CHANNEL=
|
||||
TIM_TRACKING_ACTIVITIES_CLIENT_ID=
|
||||
|
||||
# Legacy app / workflows
|
||||
TIM_WORKFLOWS_DIR=workflows
|
||||
TIM_WORKFLOW_POSTGRES_DSN=
|
||||
TIM_APP_HOST=0.0.0.0
|
||||
TIM_APP_PORT=8000
|
||||
TIM_APP_RELOAD=false
|
||||
TIM_APP_LOG_LEVEL=DEBUG
|
||||
|
||||
# LLM Gateway legado
|
||||
TIM_LLM_GATEWAY_SOURCES=txt
|
||||
TIM_LLM_GATEWAY_LOCAL_DIR=prompts
|
||||
TIM_LLM_GATEWAY_CAPABILITIES_DIR=
|
||||
TIM_LLM_GATEWAY_LANGGRAPH_URL=
|
||||
TIM_LLM_GATEWAY_LANGGRAPH_TIMEOUT=5
|
||||
TIM_LLM_GATEWAY_FALLBACK_PROMPT_ID=default
|
||||
TIM_LLM_GATEWAY_FALLBACK_PROMPT_CONTENT="Você é um agente para operações VAS TIM. Use tools quando precisar executar ações no backend."
|
||||
|
||||
# Pub/Sub / export legado
|
||||
GCP_PROJECT_ID=tim-bigdata-dev-ca1f
|
||||
AGENT_PUBSUB_TOPIC=pbs-ingest-agnt-ai-contas-curadoria
|
||||
TIM_PUBSUB_DIRECT_ENABLED=true
|
||||
GOOGLE_APPLICATION_CREDENTIALS=GCP_ACESS_KEY.json
|
||||
|
||||
# Mock legado explicitamente desligado
|
||||
TIM_MOCK_LATENCY_MS=0
|
||||
TIM_MOCK_FAILURES=
|
||||
TIM_MOCK_FIXTURES_DIR=
|
||||
28
mcp_servers/legacy_tim_mcp/README.md
Normal file
28
mcp_servers/legacy_tim_mcp/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Legacy TIM MCP Server
|
||||
|
||||
Servidor MCP HTTP simples para adaptar os serviços externos do `agent_contas_first` ao contrato esperado pelo `agent_framework_oci`:
|
||||
|
||||
- `GET /mcp/tools/list`
|
||||
- `POST /mcp/tools/call` com `{ "tool_name": "...", "arguments": {...} }`
|
||||
|
||||
Por padrão roda com integração real (`TIM_MCP_USE_MOCK=false`). Para teste local sem APIs TIM, configure explicitamente `TIM_MCP_USE_MOCK=true`.
|
||||
|
||||
## Subir local
|
||||
|
||||
```bash
|
||||
cd mcp_servers/legacy_tim_mcp
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
cp .env.example .env
|
||||
uvicorn main:app --host 0.0.0.0 --port 8100 --reload
|
||||
```
|
||||
|
||||
## Teste direto
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8100/health
|
||||
curl -s http://localhost:8100/mcp/tools/call \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"tool_name":"consultar_fatura","arguments":{"msisdn":"11999999999","invoice_id":"FAT-2026-06"}}'
|
||||
```
|
||||
BIN
mcp_servers/legacy_tim_mcp/__pycache__/main.cpython-313.pyc
Normal file
BIN
mcp_servers/legacy_tim_mcp/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
657
mcp_servers/legacy_tim_mcp/main.py
Normal file
657
mcp_servers/legacy_tim_mcp/main.py
Normal file
@@ -0,0 +1,657 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Awaitable
|
||||
from urllib.parse import urlencode, urlsplit, urlunsplit, parse_qsl
|
||||
|
||||
import httpx
|
||||
from dotenv import dotenv_values
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
def _hydrate_env_from_dotenv_files() -> list[str]:
|
||||
"""Load .env values even when the MCP server is started from another directory.
|
||||
|
||||
The previous package depended on `source .env` or Docker `env_file`. When the
|
||||
user started uvicorn directly, variables like TIM_COMPLETE_INVOICES_URL were
|
||||
absent and the MCP returned `Endpoint externo não configurado`.
|
||||
|
||||
This loader reads both the repository root .env and the MCP-local .env, and
|
||||
fills only variables that are missing or exported as an empty string.
|
||||
"""
|
||||
here = Path(__file__).resolve()
|
||||
candidates = []
|
||||
for candidate in [
|
||||
Path.cwd() / ".env",
|
||||
here.parent / ".env",
|
||||
here.parents[2] / ".env" if len(here.parents) > 2 else None,
|
||||
]:
|
||||
if candidate and candidate.exists() and candidate not in candidates:
|
||||
candidates.append(candidate)
|
||||
|
||||
loaded: list[str] = []
|
||||
for env_file in candidates:
|
||||
values = dotenv_values(env_file)
|
||||
for key, value in values.items():
|
||||
if key and value is not None and not os.getenv(key):
|
||||
os.environ[key] = str(value)
|
||||
loaded.append(str(env_file))
|
||||
return loaded
|
||||
|
||||
|
||||
LOADED_ENV_FILES = _hydrate_env_from_dotenv_files()
|
||||
|
||||
APP_NAME = os.getenv("APP_NAME", "legacy-tim-mcp-server")
|
||||
USE_MOCK = os.getenv("TIM_MCP_USE_MOCK", "false").lower() in {"1", "true", "yes", "y"}
|
||||
DEFAULT_TIMEOUT = float(os.getenv("TIM_MCP_TIMEOUT_SECONDS", os.getenv("TIM_GATEWAY_DEFAULT_TIMEOUT", "30")))
|
||||
DEFAULT_CLIENT_ID = os.getenv("TIM_CLIENT_ID", os.getenv("TIM_DEFAULT_CLIENT_ID", "AIAGENTCR"))
|
||||
DEFAULT_USER_ID = os.getenv("TIM_USER_ID", "AIAGENTCR")
|
||||
DEFAULT_CHANNEL = os.getenv("TIM_DEFAULT_CHANNEL", "APP")
|
||||
DEFAULT_CSP_ID = os.getenv("TIM_DEFAULT_CSP_ID", "740")
|
||||
|
||||
|
||||
def _first_env(*names: str, default: str = "") -> str:
|
||||
for name in names:
|
||||
value = os.getenv(name)
|
||||
if value is not None and str(value).strip() != "":
|
||||
return str(value).strip().strip('"').strip("'")
|
||||
return default
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EndpointConfig:
|
||||
name: str
|
||||
url: str
|
||||
auth: str = ""
|
||||
timeout: float = DEFAULT_TIMEOUT
|
||||
client_id: str = DEFAULT_CLIENT_ID
|
||||
extra_headers: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
class GatewayError(RuntimeError):
|
||||
"""Erro rico no padrão do legado: status, body, headers, request id e provider."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
*,
|
||||
endpoint: str,
|
||||
method: str,
|
||||
url: str,
|
||||
status_code: int | None = None,
|
||||
error_code: str | None = None,
|
||||
error_body: Any = None,
|
||||
error_headers: dict[str, str] | None = None,
|
||||
request_payload: Any = None,
|
||||
response_text: str | None = None,
|
||||
latency_ms: int | None = None,
|
||||
exception_type: str = "GatewayError",
|
||||
) -> None:
|
||||
super().__init__(message)
|
||||
self.endpoint = endpoint
|
||||
self.method = method
|
||||
self.url = url
|
||||
self.status_code = status_code
|
||||
self.error_code = error_code
|
||||
self.error_body = error_body
|
||||
self.error_headers = error_headers or {}
|
||||
self.request_payload = request_payload
|
||||
self.response_text = response_text
|
||||
self.latency_ms = latency_ms
|
||||
self.exception_type = exception_type
|
||||
|
||||
@property
|
||||
def metadata(self) -> dict[str, Any]:
|
||||
provider: dict[str, Any] = {}
|
||||
if isinstance(self.error_body, dict) and isinstance(self.error_body.get("provider"), dict):
|
||||
provider = self.error_body.get("provider") or {}
|
||||
return {
|
||||
"endpoint": self.endpoint,
|
||||
"method": self.method,
|
||||
"url": self.url,
|
||||
"status_code": self.status_code,
|
||||
"error_code": self.error_code,
|
||||
"provider_service": provider.get("serviceName"),
|
||||
"provider_error_code": provider.get("errorCode"),
|
||||
"provider_error_message": provider.get("errorMessage"),
|
||||
"message_id": _get_header_value(self.error_headers, "Messageid", "MessageId"),
|
||||
"kong_request_id": _get_header_value(self.error_headers, "X-Kong-Request-Id"),
|
||||
"latency_ms": self.latency_ms,
|
||||
"exception_type": self.exception_type,
|
||||
"request_payload_preview": _preview_json(self.request_payload),
|
||||
"response_body_preview": _preview_json(self.error_body if self.error_body is not None else self.response_text),
|
||||
"headers": _safe_headers(self.error_headers),
|
||||
"mock": USE_MOCK,
|
||||
}
|
||||
|
||||
|
||||
def _preview_json(value: Any, limit: int = 2000) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
text = str(value)
|
||||
return text if len(text) <= limit else text[:limit] + "..."
|
||||
|
||||
|
||||
def _get_header_value(headers: dict[str, str] | None, *keys: str) -> str:
|
||||
normalized = {str(k).lower(): str(v) for k, v in (headers or {}).items()}
|
||||
for key in keys:
|
||||
value = normalized.get(key.lower(), "").strip()
|
||||
if value:
|
||||
return value
|
||||
return ""
|
||||
|
||||
|
||||
def _safe_headers(headers: dict[str, str] | None) -> dict[str, str]:
|
||||
safe: dict[str, str] = {}
|
||||
for k, v in (headers or {}).items():
|
||||
lk = str(k).lower()
|
||||
if lk in {"authorization", "authorizationoam", "authorization-oam", "proxy-authorization"} or "token" in lk or "secret" in lk:
|
||||
safe[k] = "***" if v else ""
|
||||
else:
|
||||
safe[k] = str(v)
|
||||
return safe
|
||||
|
||||
|
||||
def _append_path(url: str, *parts: str) -> str:
|
||||
base = url.rstrip("/")
|
||||
suffix = "/".join(str(p).strip("/") for p in parts if str(p or "").strip("/"))
|
||||
return f"{base}/{suffix}" if suffix else base
|
||||
|
||||
|
||||
def _add_query(url: str, params: dict[str, Any]) -> str:
|
||||
clean = {k: v for k, v in params.items() if v is not None and str(v) != ""}
|
||||
if not clean:
|
||||
return url
|
||||
parts = urlsplit(url)
|
||||
query = dict(parse_qsl(parts.query, keep_blank_values=True))
|
||||
query.update(clean)
|
||||
return urlunsplit((parts.scheme, parts.netloc, parts.path, urlencode(query), parts.fragment))
|
||||
|
||||
|
||||
ENDPOINTS: dict[str, tuple[tuple[str, ...], tuple[str, ...], str, tuple[str, ...]]] = {
|
||||
"TIM_COMPLETE_INVOICES": (("TIM_COMPLETE_INVOICES_URL",), ("TIM_COMPLETE_INVOICES_AUTH",), "", ("TIM_COMPLETE_INVOICES_TIMEOUT",)),
|
||||
"TIM_PROFILE_BILL": (("TIM_PROFILE_BILL_URL", "TIM_URL_PERFIL_FATURA"), ("TIM_PROFILE_BILL_AUTH", "TIM_COMPLETE_INVOICES_AUTH"), "", ("TIM_PROFILE_BILL_TIMEOUT",)),
|
||||
"TIM_CONTRATO": (("TIM_CONTRATO_URL",), ("TIM_CONTRATO_AUTH",), "", ("TIM_CONTRATO_TIMEOUT",)),
|
||||
"TIM_QUERY_VAS": (("TIM_QUERY_VAS_URL", "TIM_URL_CONSULTA_VAS", "TIM_CONSULTA_URL"), ("TIM_QUERY_VAS_AUTH", "TIM_CONSULTA_AUTH"), "", ("TIM_QUERY_VAS_TIMEOUT", "TIM_CONSULTA_TIMEOUT")),
|
||||
"TIM_BILL_PDF": (("TIM_BILL_PDF_URL", "TIM_URL_INVOICE_RECOVER"), ("TIM_BILL_PDF_AUTH", "TIM_INVOICE_RECOVER_AUTH"), "", ("TIM_BILL_PDF_TIMEOUT", "TIM_INVOICE_RECOVER_TIMEOUT")),
|
||||
"TIM_PROTOCOL": (("TIM_PROTOCOL_URL",), ("TIM_PROTOCOL_AUTH",), "", ("TIM_PROTOCOL_TIMEOUT",)),
|
||||
"TIM_CUSTOMER_CONTESTATION": (("TIM_CUSTOMER_CONTESTATION_URL", "TIM_DIVERGENCIA_URL"), ("TIM_CUSTOMER_CONTESTATION_AUTH", "TIM_DIVERGENCIA_AUTH"), "", ("TIM_CUSTOMER_CONTESTATION_TIMEOUT",)),
|
||||
"TIM_SERVICE_REQUEST_STATUS": (("TIM_SERVICE_REQUEST_STATUS_URL",), ("TIM_SERVICE_REQUEST_STATUS_AUTH",), "", ("TIM_SERVICE_REQUEST_STATUS_TIMEOUT",)),
|
||||
"TIM_CANCEL_VAS": (("TIM_CANCEL_VAS_URL", "TIM_CANCELLATION_URL", "TIM_CANCELAMENTO_URL"), ("TIM_CANCEL_VAS_AUTH", "TIM_CANCELLATION_AUTH", "TIM_CANCELAMENTO_AUTH"), "", ("TIM_CANCEL_VAS_TIMEOUT", "TIM_CANCELAMENTO_TIMEOUT")),
|
||||
"TIM_BLOCK_VAS": (("TIM_BLOCK_VAS_URL", "TIM_URL_BLOQUEIO_VAS", "TIM_BLOQUEIO_URL"), ("TIM_BLOCK_VAS_AUTH", "TIM_BLOQUEIO_AUTH"), "", ("TIM_BLOCK_VAS_TIMEOUT", "TIM_BLOQUEIO_TIMEOUT")),
|
||||
"TIM_SMS": (("TIM_SMS_URL",), ("TIM_SMS_AUTH", "SMS_BARCODE_AUTH"), "", ("TIM_SMS_TIMEOUT",)),
|
||||
"TIM_TRACKING_ACTIVITIES": (("TIM_TRACKING_ACTIVITIES_URL",), ("TIM_TRACKING_ACTIVITIES_AUTH",), "", ("TIM_TRACKING_ACTIVITIES_TIMEOUT",)),
|
||||
}
|
||||
|
||||
|
||||
def endpoint_config(name: str) -> EndpointConfig:
|
||||
url_names, auth_names, default_url, timeout_names = ENDPOINTS[name]
|
||||
timeout_raw = _first_env(*timeout_names, default=str(DEFAULT_TIMEOUT))
|
||||
try:
|
||||
timeout = float(timeout_raw)
|
||||
except ValueError:
|
||||
timeout = DEFAULT_TIMEOUT
|
||||
return EndpointConfig(
|
||||
name=name,
|
||||
url=_first_env(*url_names, default=default_url),
|
||||
auth=_first_env(*auth_names, "TIM_DEFAULT_AUTH", default=""),
|
||||
timeout=timeout,
|
||||
client_id=_first_env(f"{name}_CLIENT_ID", "TIM_CLIENT_ID", "TIM_DEFAULT_CLIENT_ID", default=DEFAULT_CLIENT_ID),
|
||||
)
|
||||
|
||||
|
||||
class HttpApiGateway:
|
||||
async def request(
|
||||
self,
|
||||
method: str,
|
||||
config: EndpointConfig,
|
||||
*,
|
||||
url: str | None = None,
|
||||
payload: dict[str, Any] | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
if not config.url:
|
||||
raise GatewayError(
|
||||
f"Endpoint externo não configurado para {config.name}",
|
||||
endpoint=config.name,
|
||||
method=method,
|
||||
url=url or "",
|
||||
request_payload=payload,
|
||||
exception_type="MissingEndpoint",
|
||||
)
|
||||
|
||||
target_url = url or config.url
|
||||
request_headers = {"Accept": "application/json", **(headers or {})}
|
||||
if config.auth and "Authorization" not in request_headers:
|
||||
request_headers["Authorization"] = config.auth
|
||||
if payload is not None:
|
||||
request_headers.setdefault("Content-Type", "application/json")
|
||||
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=config.timeout) as client:
|
||||
resp = await client.request(method, target_url, json=payload if method.upper() != "GET" else None, headers=request_headers)
|
||||
latency_ms = int((time.monotonic() - t0) * 1000)
|
||||
parsed: Any
|
||||
try:
|
||||
parsed = resp.json()
|
||||
except Exception:
|
||||
parsed = {"raw_text": resp.text}
|
||||
metadata = {
|
||||
"endpoint": config.name,
|
||||
"method": method,
|
||||
"url": target_url,
|
||||
"status_code": resp.status_code,
|
||||
"latency_ms": latency_ms,
|
||||
"request_headers": _safe_headers(request_headers),
|
||||
"response_headers": _safe_headers(dict(resp.headers)),
|
||||
"request_payload_preview": _preview_json(payload),
|
||||
"response_body_preview": _preview_json(parsed),
|
||||
"mock": False,
|
||||
}
|
||||
if resp.status_code >= 400:
|
||||
code = None
|
||||
if isinstance(parsed, dict):
|
||||
code = parsed.get("error_code") or parsed.get("code") or parsed.get("errorCode") or f"HTTP_{resp.status_code}"
|
||||
raise GatewayError(
|
||||
f"{config.name} falhou: status={resp.status_code} body={_preview_json(parsed, 500)}",
|
||||
endpoint=config.name,
|
||||
method=method,
|
||||
url=target_url,
|
||||
status_code=resp.status_code,
|
||||
error_code=str(code or f"HTTP_{resp.status_code}"),
|
||||
error_body=parsed,
|
||||
error_headers=dict(resp.headers),
|
||||
request_payload=payload,
|
||||
latency_ms=latency_ms,
|
||||
exception_type="HTTPStatusError",
|
||||
)
|
||||
if isinstance(parsed, dict):
|
||||
parsed.setdefault("metadata", metadata)
|
||||
return parsed
|
||||
return {"value": parsed, "metadata": metadata}
|
||||
except GatewayError:
|
||||
raise
|
||||
except httpx.TimeoutException as exc:
|
||||
latency_ms = int((time.monotonic() - t0) * 1000)
|
||||
raise GatewayError(
|
||||
f"{config.name} timeout após {config.timeout}s: {exc}",
|
||||
endpoint=config.name,
|
||||
method=method,
|
||||
url=target_url,
|
||||
request_payload=payload,
|
||||
latency_ms=latency_ms,
|
||||
exception_type="TimeoutException",
|
||||
) from exc
|
||||
except httpx.RequestError as exc:
|
||||
latency_ms = int((time.monotonic() - t0) * 1000)
|
||||
raise GatewayError(
|
||||
f"{config.name} erro de transporte: {exc}",
|
||||
endpoint=config.name,
|
||||
method=method,
|
||||
url=target_url,
|
||||
request_payload=payload,
|
||||
latency_ms=latency_ms,
|
||||
exception_type=exc.__class__.__name__,
|
||||
) from exc
|
||||
|
||||
async def get(self, config: EndpointConfig, url: str | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
return await self.request("GET", config, url=url, headers=headers)
|
||||
|
||||
async def post(self, config: EndpointConfig, payload: dict[str, Any], url: str | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
print("===========================================================================")
|
||||
print(config, payload)
|
||||
print("===========================================================================")
|
||||
return await self.request("POST", config, url=url, payload=payload, headers=headers)
|
||||
|
||||
async def delete(self, config: EndpointConfig, payload: dict[str, Any] | None = None, url: str | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
return await self.request("DELETE", config, url=url, payload=payload, headers=headers)
|
||||
|
||||
|
||||
def _common_headers(config: EndpointConfig, *, client_id: str | None = None) -> dict[str, str]:
|
||||
h = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if config.auth:
|
||||
h["Authorization"] = config.auth
|
||||
cid = client_id or config.client_id or DEFAULT_CLIENT_ID
|
||||
if cid:
|
||||
h["ClientID"] = cid
|
||||
h["clientId"] = cid
|
||||
return h
|
||||
|
||||
|
||||
def _arg(args: dict[str, Any], *names: str, default: Any = "") -> Any:
|
||||
for name in names:
|
||||
value = args.get(name)
|
||||
if value is not None and str(value).strip() != "":
|
||||
return value
|
||||
return default
|
||||
|
||||
|
||||
class LegacyTimCommand:
|
||||
command_name = "LegacyTimCommand"
|
||||
endpoint_name = ""
|
||||
|
||||
def __init__(self, args: dict[str, Any], gateway: HttpApiGateway) -> None:
|
||||
self.args = args or {}
|
||||
self.gateway = gateway
|
||||
self.config = endpoint_config(self.endpoint_name)
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
def envelope(self, raw: dict[str, Any], *, normalized: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
meta = raw.get("metadata", {}) if isinstance(raw, dict) else {}
|
||||
return {
|
||||
"command": self.command_name,
|
||||
"endpoint": self.endpoint_name,
|
||||
"ok": True,
|
||||
"data": raw,
|
||||
"normalized": normalized or {},
|
||||
"metadata": {**meta, "command": self.command_name, "endpoint": self.endpoint_name, "mock": USE_MOCK},
|
||||
}
|
||||
|
||||
|
||||
class CompleteInvoicesCommand(LegacyTimCommand):
|
||||
command_name = "CompleteInvoicesCommand"
|
||||
endpoint_name = "TIM_COMPLETE_INVOICES"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
payload = {"msisdn": msisdn}
|
||||
invoice_id = _arg(self.args, "invoice_id", "invoiceId", "contract_key")
|
||||
if invoice_id:
|
||||
payload["invoiceId"] = str(invoice_id)
|
||||
raw = await self.gateway.post(self.config, payload, headers=_common_headers(self.config, client_id=_first_env("TIM_COMPLETE_INVOICES_CLIENT_ID", default=DEFAULT_CLIENT_ID)))
|
||||
items = raw.get("paymentItems") if isinstance(raw, dict) else None
|
||||
selected = None
|
||||
if isinstance(items, list) and invoice_id:
|
||||
for item in items:
|
||||
if isinstance(item, dict) and str(item.get("id") or item.get("invoiceId") or item.get("number") or "") == str(invoice_id):
|
||||
selected = item
|
||||
break
|
||||
if selected is None and isinstance(items, list) and items:
|
||||
selected = next((i for i in items if isinstance(i, dict)), None)
|
||||
normalized = {
|
||||
"msisdn": msisdn,
|
||||
"invoice_id": invoice_id or (selected or {}).get("id") or (selected or {}).get("invoiceId"),
|
||||
"amount": (selected or {}).get("amount") or (selected or {}).get("totalAmount") or raw.get("totalAmount") if isinstance(raw, dict) else None,
|
||||
"due_date": (selected or {}).get("dueDate") or (selected or {}).get("expirationDate"),
|
||||
"status": (selected or {}).get("status"),
|
||||
"payment_items_count": len(items) if isinstance(items, list) else None,
|
||||
}
|
||||
return self.envelope(raw, normalized=normalized)
|
||||
|
||||
|
||||
class ProfileBillCommand(CompleteInvoicesCommand):
|
||||
command_name = "ProfileBillCommand"
|
||||
endpoint_name = "TIM_PROFILE_BILL"
|
||||
|
||||
|
||||
class QueryVasCommand(LegacyTimCommand):
|
||||
command_name = "QueryVasCommand"
|
||||
endpoint_name = "TIM_QUERY_VAS"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
url = _append_path(self.config.url, msisdn)
|
||||
url = _add_query(url, {"clientid": _first_env("TIM_CONSULTA_CLIENT_ID", "TIM_CLIENT_ID", default=DEFAULT_CLIENT_ID)})
|
||||
raw = await self.gateway.get(self.config, url=url, headers=_common_headers(self.config))
|
||||
services = raw.get("services") or raw.get("items") or raw.get("products") if isinstance(raw, dict) else None
|
||||
normalized = {"msisdn": msisdn, "service_count": len(services) if isinstance(services, list) else None}
|
||||
return self.envelope(raw, normalized=normalized)
|
||||
|
||||
|
||||
class ContractInformationCommand(LegacyTimCommand):
|
||||
command_name = "ContractInformationCommand"
|
||||
endpoint_name = "TIM_CONTRATO"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
url = _append_path(self.config.url, msisdn)
|
||||
raw = await self.gateway.get(self.config, url=url, headers=_common_headers(self.config))
|
||||
return self.envelope(raw, normalized={"msisdn": msisdn, "plan": raw.get("plan") if isinstance(raw, dict) else None})
|
||||
|
||||
|
||||
class InvoiceRecoverCommand(LegacyTimCommand):
|
||||
command_name = "InvoiceRecoverCommand"
|
||||
endpoint_name = "TIM_BILL_PDF"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
invoice_id = str(_arg(self.args, "invoice_id", "invoiceId", "contract_key"))
|
||||
url = _add_query(self.config.url, {"invoiceId": invoice_id, "msisdn": msisdn, "customerId": _arg(self.args, "customer_id", "customerId")})
|
||||
raw = await self.gateway.get(self.config, url=url, headers=_common_headers(self.config, client_id=_first_env("TIM_INVOICE_RECOVER_CLIENT_ID", "TIM_BILL_PDF_CLIENT_ID", default="TIMX")))
|
||||
return self.envelope(raw, normalized={"msisdn": msisdn, "invoice_id": invoice_id, "pdf_available": bool(raw)})
|
||||
|
||||
|
||||
class ProtocolCommand(LegacyTimCommand):
|
||||
command_name = "ProtocolCommand"
|
||||
endpoint_name = "TIM_PROTOCOL"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
payload = {
|
||||
"customer": {"msisdn": msisdn},
|
||||
"protocolNumber": _arg(self.args, "protocol_number", "protocolNumber"),
|
||||
"reason": _arg(self.args, "reason", default="Atendimento Agent Contas FIRST"),
|
||||
"channel": _arg(self.args, "channel", default=DEFAULT_CHANNEL),
|
||||
"interactionCallId": _arg(self.args, "interaction_call_id", "ura_call_id", "message_id"),
|
||||
}
|
||||
raw = await self.gateway.post(self.config, payload, headers=_common_headers(self.config))
|
||||
return self.envelope(raw, normalized={"msisdn": msisdn, "protocol_number": raw.get("protocolNumber") or payload["protocolNumber"] if isinstance(raw, dict) else payload["protocolNumber"]})
|
||||
|
||||
|
||||
class CustomerContestationCommand(LegacyTimCommand):
|
||||
command_name = "CustomerContestationCommand"
|
||||
endpoint_name = "TIM_CUSTOMER_CONTESTATION"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
items = _arg(self.args, "items", default=[])
|
||||
if not isinstance(items, list):
|
||||
items = [items]
|
||||
payload = {
|
||||
"customer": {"msisdn": msisdn},
|
||||
"invoice": {"number": _arg(self.args, "invoice_id", "invoice_number", "contract_key")},
|
||||
"contestation": {
|
||||
"reason": _arg(self.args, "reason", default="Contestação solicitada pelo atendimento"),
|
||||
"claimedAmount": _arg(self.args, "amount", "claimed_amount", default=0),
|
||||
"items": items,
|
||||
},
|
||||
"user": {"login": _first_env("TIM_CUSTOMER_CONTESTATION_USER_ID", default=DEFAULT_USER_ID)},
|
||||
"clientId": _first_env("TIM_CUSTOMER_CONTESTATION_CLIENT_ID", default=DEFAULT_CLIENT_ID),
|
||||
}
|
||||
raw = await self.gateway.post(self.config, payload, headers=_common_headers(self.config, client_id=payload["clientId"]))
|
||||
return self.envelope(raw, normalized={"msisdn": msisdn, "sr": raw.get("sr") or raw.get("serviceRequest") if isinstance(raw, dict) else None})
|
||||
|
||||
|
||||
class ServiceRequestStatusCommand(LegacyTimCommand):
|
||||
command_name = "ServiceRequestStatusCommand"
|
||||
endpoint_name = "TIM_SERVICE_REQUEST_STATUS"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
protocol = _arg(self.args, "sr", "protocol_number", "protocolNumber", "service_request")
|
||||
msisdn = _arg(self.args, "msisdn", "customer_id")
|
||||
url = _add_query(self.config.url, {"protocolNumber": protocol, "serviceRequest": protocol, "msisdn": msisdn})
|
||||
raw = await self.gateway.get(self.config, url=url, headers=_common_headers(self.config))
|
||||
return self.envelope(raw, normalized={"protocol_number": protocol, "status": raw.get("status") if isinstance(raw, dict) else None})
|
||||
|
||||
|
||||
class CancelVasCommand(LegacyTimCommand):
|
||||
command_name = "CancelVasCommand"
|
||||
endpoint_name = "TIM_CANCEL_VAS"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
service_id = _arg(self.args, "service_id", "app_id", "product_id")
|
||||
payload = {"msisdn": msisdn, "serviceId": service_id, "cspId": _arg(self.args, "csp_id", default=DEFAULT_CSP_ID)}
|
||||
raw = await self.gateway.delete(self.config, payload, headers=_common_headers(self.config))
|
||||
return self.envelope(raw, normalized={"msisdn": msisdn, "service_id": service_id, "cancelled": True})
|
||||
|
||||
|
||||
class BlockVasCommand(LegacyTimCommand):
|
||||
command_name = "BlockVasCommand"
|
||||
endpoint_name = "TIM_BLOCK_VAS"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
payload = {
|
||||
"msisdn": msisdn,
|
||||
"operationType": _first_env("TIM_BLOCK_VAS_OPERATION_TYPE", "TIM_BLOQUEIO_OPERATION_TYPE", default="block"),
|
||||
"cspId": _arg(self.args, "csp_id", default=DEFAULT_CSP_ID),
|
||||
"serviceId": _arg(self.args, "service_id", "app_id", "product_id"),
|
||||
}
|
||||
headers = _common_headers(self.config)
|
||||
accept_encoding = _first_env("TIM_BLOCK_VAS_ACCEPT_ENCODING", "TIM_BLOQUEIO_ACCEPT_ENCODING", default="")
|
||||
if accept_encoding:
|
||||
headers["Accept-Encoding"] = accept_encoding
|
||||
raw = await self.gateway.post(self.config, payload, headers=headers)
|
||||
return self.envelope(raw, normalized={"msisdn": msisdn, "blocked": True})
|
||||
|
||||
|
||||
class SmsCommand(LegacyTimCommand):
|
||||
command_name = "SmsCommand"
|
||||
endpoint_name = "TIM_SMS"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
payload = {
|
||||
"msisdn": msisdn,
|
||||
"message": _arg(self.args, "message", default=""),
|
||||
"longUrl": _arg(self.args, "long_url", "longUrl", default=""),
|
||||
"notifyUrl": _arg(self.args, "notify_url", "notifyUrl", default=""),
|
||||
}
|
||||
raw = await self.gateway.post(self.config, payload, headers=_common_headers(self.config, client_id=_first_env("TIM_SMS_CLIENT_ID", default="")))
|
||||
return self.envelope(raw, normalized={"msisdn": msisdn, "sent": True})
|
||||
|
||||
|
||||
class TrackingActivitiesCommand(LegacyTimCommand):
|
||||
command_name = "TrackingActivitiesCommand"
|
||||
endpoint_name = "TIM_TRACKING_ACTIVITIES"
|
||||
|
||||
async def execute(self) -> dict[str, Any]:
|
||||
msisdn = str(_arg(self.args, "msisdn", "customer_id", "customer_key"))
|
||||
payload = {
|
||||
"channel": _first_env("TIM_TRACKING_ACTIVITIES_CHANNEL", default=DEFAULT_CHANNEL),
|
||||
"customer": {"msisdn": msisdn},
|
||||
"protocolNumber": _arg(self.args, "protocol_number", "protocolNumber"),
|
||||
"invoice": {"number": _arg(self.args, "invoice_id", "invoice_number")},
|
||||
"activity": {
|
||||
"type": _arg(self.args, "activity_type", default="ATENDIMENTO"),
|
||||
"status": _arg(self.args, "activity_status", default="OPEN"),
|
||||
"id": _arg(self.args, "activity_id", default=""),
|
||||
},
|
||||
"user": {"login": _first_env("TIM_TRACKING_ACTIVITIES_USER_LOGIN", default=DEFAULT_USER_ID)},
|
||||
}
|
||||
raw = await self.gateway.post(self.config, payload, headers=_common_headers(self.config, client_id=_first_env("TIM_TRACKING_ACTIVITIES_CLIENT_ID", default=DEFAULT_CLIENT_ID)))
|
||||
return self.envelope(raw, normalized={"msisdn": msisdn, "tracked": True})
|
||||
|
||||
|
||||
COMMANDS: dict[str, type[LegacyTimCommand]] = {
|
||||
"consultar_fatura": CompleteInvoicesCommand,
|
||||
"consultar_pagamentos": ProfileBillCommand,
|
||||
"consultar_plano": ContractInformationCommand,
|
||||
"listar_servicos": QueryVasCommand,
|
||||
"recuperar_pdf_fatura": InvoiceRecoverCommand,
|
||||
"registrar_protocolo": ProtocolCommand,
|
||||
"abrir_contestacao": CustomerContestationCommand,
|
||||
"consultar_status_sr": ServiceRequestStatusCommand,
|
||||
"atualizar_status_sr": ServiceRequestStatusCommand,
|
||||
"cancelar_vas": CancelVasCommand,
|
||||
"bloquear_vas": BlockVasCommand,
|
||||
"enviar_sms": SmsCommand,
|
||||
"registrar_tracking": TrackingActivitiesCommand,
|
||||
}
|
||||
|
||||
|
||||
MOCKS: dict[str, Callable[[dict[str, Any]], dict[str, Any]]] = {
|
||||
"consultar_fatura": lambda a: {"paymentItems": [{"id": _arg(a, "invoice_id", default="3000131180"), "amount": 123.45, "dueDate": "2026-06-20", "status": "em aberto"}]},
|
||||
"consultar_pagamentos": lambda a: {"payments": []},
|
||||
"consultar_plano": lambda a: {"plan": "TIM Controle Smart"},
|
||||
"listar_servicos": lambda a: {"services": []},
|
||||
}
|
||||
|
||||
|
||||
class ToolCall(BaseModel):
|
||||
tool_name: str = Field(..., alias="tool_name")
|
||||
arguments: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
app = FastAPI(title=APP_NAME)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health() -> dict[str, Any]:
|
||||
configured = {name: bool(endpoint_config(name).url) for name in ENDPOINTS}
|
||||
urls = {name: endpoint_config(name).url for name in ENDPOINTS}
|
||||
return {
|
||||
"ok": True,
|
||||
"app": APP_NAME,
|
||||
"mock": USE_MOCK,
|
||||
"tool_count": len(COMMANDS),
|
||||
"loaded_env_files": LOADED_ENV_FILES,
|
||||
"configured_endpoints": configured,
|
||||
"endpoint_urls": urls,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/debug/config")
|
||||
async def debug_config() -> dict[str, Any]:
|
||||
return {
|
||||
"loaded_env_files": LOADED_ENV_FILES,
|
||||
"mock": USE_MOCK,
|
||||
"endpoints": {
|
||||
name: {
|
||||
"url": endpoint_config(name).url,
|
||||
"has_auth": bool(endpoint_config(name).auth),
|
||||
"timeout": endpoint_config(name).timeout,
|
||||
"client_id": endpoint_config(name).client_id,
|
||||
}
|
||||
for name in ENDPOINTS
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.get("/mcp/tools/list")
|
||||
async def list_tools() -> dict[str, Any]:
|
||||
return {"tools": [{"name": name, "description": cls.command_name} for name, cls in COMMANDS.items()]}
|
||||
|
||||
|
||||
@app.post("/mcp/tools/call")
|
||||
async def call_tool(call: ToolCall) -> dict[str, Any]:
|
||||
base_metadata = {"server": "legacy_tim", "tool": call.tool_name, "mock": USE_MOCK}
|
||||
command_cls = COMMANDS.get(call.tool_name)
|
||||
if command_cls is None:
|
||||
return {"ok": False, "result": None, "error": f"Tool não encontrada: {call.tool_name}", "metadata": {**base_metadata, "exception_type": "ToolNotFound"}}
|
||||
|
||||
try:
|
||||
if USE_MOCK:
|
||||
raw = MOCKS.get(call.tool_name, lambda a: {"mock": True})(call.arguments or {})
|
||||
result = {"command": command_cls.command_name, "ok": True, "data": raw, "metadata": {**base_metadata, "command": command_cls.command_name, "endpoint": command_cls.endpoint_name}}
|
||||
else:
|
||||
result = await command_cls(call.arguments or {}, HttpApiGateway()).execute()
|
||||
metadata = dict(result.get("metadata") or {}) if isinstance(result, dict) else {}
|
||||
ok = bool(result.get("ok", True)) if isinstance(result, dict) else True
|
||||
error = str(result.get("error") or "") if isinstance(result, dict) else ""
|
||||
return {"ok": ok, "result": result, "error": error or None, "metadata": {**base_metadata, **metadata}}
|
||||
except GatewayError as exc:
|
||||
return {
|
||||
"ok": False,
|
||||
"result": None,
|
||||
"error": str(exc) or exc.exception_type,
|
||||
"metadata": {**base_metadata, **exc.metadata},
|
||||
}
|
||||
except Exception as exc:
|
||||
return {
|
||||
"ok": False,
|
||||
"result": None,
|
||||
"error": str(exc) or exc.__class__.__name__,
|
||||
"metadata": {**base_metadata, "exception_type": exc.__class__.__name__},
|
||||
}
|
||||
5
mcp_servers/legacy_tim_mcp/requirements.txt
Normal file
5
mcp_servers/legacy_tim_mcp/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
fastapi>=0.111.0
|
||||
uvicorn[standard]>=0.30.0
|
||||
httpx>=0.27.0
|
||||
pydantic>=2.7.0
|
||||
python-dotenv>=1.0.1
|
||||
Reference in New Issue
Block a user