ccv2-logs-mcp

ccv2-logs-mcp

Enables AI agents to search and analyze SAP Commerce Cloud (CCv2) runtime logs via OpenSearch Dashboards using a session cookie, providing read-only tools for full-text search and error aggregation.

Category
访问服务器

README

ccv2-logs-mcp

CI License: MIT Node

Search SAP Commerce Cloud (CCv2) runtime logs from your AI agent, without leaving the conversation.

SAP does not expose a public API for CCv2 runtime logs — the only supported access is the SAP Cloud Logging dashboard (OpenSearch Dashboards 2.x) in your browser. This MCP server reuses the SSO session cookie from that browser to query the same backend the dashboard uses, and exposes three read-only tools: full-text search, ERROR aggregation, and anchored context windows.

Read-only. No writes, no deletes, no configuration changes. Works with any CCv2 project.

Quick path

  1. npm install && npm run build
  2. Export CCV2_LOGS_HOST_<ENV> for each environment you want to reach.
  3. Extract a session cookie: node scripts/extract-cookie.mjs s1
  4. Register the server with Claude Code and ask: "any errors on s1 in the last hour?"

Full detail for each step is below.

How it works

  Chrome (dedicated profile, SSO done manually, --remote-debugging-port=9222)
       |
       |  scripts/extract-cookie.mjs <env>   (CDP Network.getCookies, 127.0.0.1 only)
       v
  .ccv2-session-<env>          file, mode 0600, gitignored
       |
       |  re-read on every request (refresh the file, no restart needed)
       v
  +-----------------------------------------------+
  |  ccv2-logs-mcp  (stdio MCP server)            |
  |                                               |
  |  tools/   MCP adapters, zod schemas           |
  |  query/   pure OpenSearch DSL builders        |
  |  parse/   pure response -> typed result       |
  |  config/  env -> host + session file          |
  |  client/  the only HTTP + fs boundary         |
  +-----------------------------------------------+
       |
       |  POST https://<host>/internal/search/opensearch-with-long-numerals
       |  Cookie: <session>   osd-xsrf: true
       v
  SAP Cloud Logging (OpenSearch Dashboards 2.x)  index pattern logs-json-*

The MCP client (Claude Code, or any stdio MCP host) talks to the server over stdio. There is no HTTP listener, no daemon, and no credential storage beyond the cookie file you create yourself.

Requirements

Requirement Why
Node.js >= 20.19 ESM, native fetch, and the toolchain the test suite is pinned to
A Chromium-based browser Needed once per session to complete SSO (including 2FA) interactively
SAP Cloud Logging access Your account must already be able to open the environment's Logging dashboard

Install

git clone https://github.com/bfernandez2-seidor/ccv2-logs-mcp.git
cd ccv2-logs-mcp
npm install
npm run build

The build emits dist/index.js, which is what the MCP client executes.

Configuration

One environment variable per environment, named after the environment in uppercase:

CCV2_LOGS_HOST_S1=<dashboards host for s1>
CCV2_LOGS_HOST_P2=<dashboards host for p2>
CCV2_LOGS_HOST_D1=<dashboards host for d1>

Where to find the host. SAP Cloud Portal -> your environment -> Logging. Open the OpenSearch Dashboards URL and copy the hostname only, without scheme or path (for example dashboards-xxxxxxxx.cloud.logs.services.<region>.example).

The environment name is free-form and case-insensitive: CCV2_LOGS_HOST_STAGING makes env: "staging" valid. Any environment without a configured host is rejected with unknown_env, and the error lists the ones that are configured.

Variable Required Default Purpose
CCV2_LOGS_HOST_<ENV> yes, one per environment Dashboards hostname for <ENV>
CCV2_SESSION_DIR recommended process working directory Directory holding the .ccv2-session-<env> files

Set CCV2_SESSION_DIR to the absolute path of the clone. scripts/extract-cookie.mjs always writes the cookie to the package root, while the server looks for it relative to whatever working directory the MCP client happens to launch it in. Setting CCV2_SESSION_DIR explicitly removes the ambiguity.

Session workflow

The cookie is short-lived — roughly one hour in practice. The flow is deliberately manual: SSO with 2FA cannot be automated, and it should not be.

First time, per working session:

  1. Launch a dedicated Chrome instance with remote debugging enabled and its own profile, so your everyday browser is never exposed on the debugging port:

    google-chrome \
      --remote-debugging-port=9222 \
      --user-data-dir="$HOME/.ccv2-logs-chrome"
    
  2. In that window, open the environment's Logging endpoint from SAP Cloud Portal and complete SSO manually, including 2FA. Leave the tab open.

  3. Extract the cookie into .ccv2-session-<env>:

    node scripts/extract-cookie.mjs s1
    

    The script finds the Cloud Logging tab over CDP, reads its cookies, and writes them to .ccv2-session-s1 with mode 0600. It prints the host it read them from, so you can confirm you extracted from the right environment.

When the session expires (tools start returning expired_session): switch back to that Chrome window, reload the dashboard tab so SSO refreshes, and re-run the script. The server re-reads the file on every request, so there is nothing to restart.

Repeat step 3 with a different <env> argument for each environment — one cookie file per environment, extracted from a tab pointing at that environment.

Register with Claude Code

claude mcp add --transport stdio --scope user ccv2-logs \
  --env CCV2_LOGS_HOST_S1=<host> \
  --env CCV2_SESSION_DIR=/absolute/path/to/ccv2-logs-mcp \
  -- /absolute/path/to/node20/bin/node /absolute/path/to/ccv2-logs-mcp/dist/index.js

Pass one --env CCV2_LOGS_HOST_<ENV>=<host> per environment. Three details that matter:

  • Use an absolute path to the Node binary, not plain node. The registered command resolves through the MCP client's PATH, which may point at an older Node than the one you installed with (this package requires Node >= 20.19). Find yours with command -v node in the shell where npm install succeeded (e.g. ~/.nvm/versions/node/v20.19.6/bin/node).
  • Use absolute paths everywhere else too — the MCP client's working directory is not guaranteed to be the clone.
  • --scope user makes the server available in every project. Without it, claude mcp add registers the server only for the directory you run it from.

A newly registered MCP server is not loaded into already-running sessions: restart Claude Code (or start a new session), verify with claude mcp list, then ask the agent something like "summarize errors on s1 for the last 6 hours".

Tools

All three tools take an env argument naming a configured environment. Time arguments accept ISO 8601 timestamps or OpenSearch date math (now, now-1h, now-7d). Results are returned as JSON text.

Messages longer than 500 characters are truncated with a …[truncated] marker; the untruncated length is always reported in messageLength, so you can tell a genuinely short message from a clipped one.

search_logs

Full-text search over runtime logs, newest first.

Parameter Type Default Notes
env string Environment name, e.g. s1
query string Free text over message, msg, and logger name. Terms are ANDed
aspect string accstorefront, backoffice, backgroundProcessing, api. Case-insensitive
level string ERROR, WARN, INFO, DEBUG. Case-insensitive
from string now-1h Start of range
to string now End of range
limit integer 1–500 50 Lines to return
offset integer >= 0 0 Lines to skip. offset + limit must be <= 10000
// search_logs { env: "s1", query: "timeout", level: "ERROR", from: "now-2h" }
{
  "total": 128,
  "totalRelation": "eq",
  "returned": 2,
  "offset": 0,
  "logs": [
    {
      "timestamp": "2026-07-25T10:04:11.482Z",
      "level": "ERROR",
      "aspect": "accstorefront",
      "container": "accstorefront",
      "pod": "accstorefront-5f9c7d8b6c-h2xql",
      "logger": "com.example.checkout.PaymentServiceImpl",
      "thread": "http-nio-8080-exec-12",
      "message": "Payment gateway timeout after 30000 ms for order 000012345",
      "messageLength": 58
    },
    {
      "timestamp": "2026-07-25T10:03:58.117Z",
      "level": "ERROR",
      "aspect": "api",
      "container": "api",
      "pod": "api-6d4b9c7f55-q8n2v",
      "logger": "com.example.integration.OrderExportJob",
      "thread": "backgroundProcessing-worker-4",
      "message": "Read timeout calling downstream order service",
      "messageLength": 45
    }
  ]
}

totalRelation is gte when OpenSearch stopped counting past its tracking limit — treat total as a lower bound in that case.

error_summary

Aggregates ERROR-level lines by logger over a range. The ERROR filter is intrinsic to the tool and is not parameterizable. Use it to find what is failing before drilling in with search_logs.

Parameter Type Default Notes
env string Environment name
from string now-1h Start of range
to string now End of range
aspect string Optional aspect filter
size integer 1–100 20 Maximum number of distinct loggers returned
// error_summary { env: "s1", from: "now-6h" }
{
  "total": 342,
  "totalRelation": "eq",
  "groups": [
    {
      "logger": "com.example.checkout.PaymentServiceImpl",
      "count": 210,
      "aspects": [
        { "aspect": "accstorefront", "count": 190 },
        { "aspect": "api", "count": 20 }
      ],
      "sample": "Payment gateway timeout after 30000 ms for order 000012345"
    },
    {
      "logger": "com.example.integration.OrderExportJob",
      "count": 132,
      "aspects": [{ "aspect": "backgroundProcessing", "count": 132 }],
      "sample": "Read timeout calling downstream order service"
    }
  ]
}

An empty range is not an error: zero ERROR lines yields groups: [].

log_context

Retrieves the log lines surrounding an anchor event, scoped to the same pod, merged into one chronological window. This is how you turn "here is the exception" into "here is what the pod was doing around it".

Parameter Type Default Notes
env string Environment name
timestamp string Anchor timestamp, ISO 8601. Copy it from a search_logs result
pod string Pod name. Copy it from a search_logs result
before integer 0–200 20 Lines before the anchor
after integer 0–200 20 Lines after the anchor
// log_context { env: "s1", timestamp: "2026-07-25T10:04:11.482Z", pod: "accstorefront-5f9c7d8b6c-h2xql", before: 2, after: 1 }
{
  "anchorIndex": 2,
  "logs": [
    { "timestamp": "2026-07-25T10:04:11.402Z", "level": "INFO",  "logger": "com.example.checkout.CartFacade", "message": "Placing order 000012345", "messageLength": 24, "pod": "accstorefront-5f9c7d8b6c-h2xql" },
    { "timestamp": "2026-07-25T10:04:11.451Z", "level": "DEBUG", "logger": "com.example.checkout.PaymentServiceImpl", "message": "Authorizing 149.90 EUR", "messageLength": 22, "pod": "accstorefront-5f9c7d8b6c-h2xql" },
    { "timestamp": "2026-07-25T10:04:11.482Z", "level": "ERROR", "logger": "com.example.checkout.PaymentServiceImpl", "message": "Payment gateway timeout after 30000 ms for order 000012345", "messageLength": 58, "pod": "accstorefront-5f9c7d8b6c-h2xql" },
    { "timestamp": "2026-07-25T10:04:11.503Z", "level": "WARN",  "logger": "com.example.checkout.CartFacade", "message": "Rolling back order 000012345", "messageLength": 28, "pod": "accstorefront-5f9c7d8b6c-h2xql" }
  ]
}

anchorIndex points at the anchor line inside logs.

Context is positional, not identity-based. Log lines carry no stable document identity here, so the anchor is resolved by position: if several lines share the exact anchor instant, the first one encountered is treated as the anchor and the rest fall into the before-window. The window is still correct and complete; only which of the tied lines is labelled "the anchor" is arbitrary.

Security model

This tool holds a live SSO session for a production-capable system. Treat it accordingly.

Concern Position
Cookies are secrets They live only in .ccv2-session-<env> files, mode 0600, matched by .gitignore. Never in source, never in test fixtures, never in logs or error messages
Cookies are never bundled The files allowlist in package.json ships dist, scripts, README.md, LICENSE — nothing else
Debug port exposure The extraction script only talks to 127.0.0.1:9222. Launch Chrome with a dedicated --user-data-dir so the debugging port never exposes your everyday profile
Blast radius The server issues read-only search requests. It cannot write, delete, or reconfigure anything
PII in production logs Production runtime logs can contain customer data. Prefer staging environments for exploratory work, and keep production queries narrow and purposeful
Transport stdio only. No network listener is opened by this server

Known advisory

npm audit reports a moderate advisory for @hono/node-server <2.0.5 (path traversal in serve-static on Windows), reached transitively through @modelcontextprotocol/sdk.

Not applicable to this server. ccv2-logs-mcp is stdio-only: it imports StdioServerTransport and never loads the SDK's HTTP transport, so serve-static is never on any code path here. The advisory is tracked upstream in the SDK and will clear when the SDK bumps its dependency.

Troubleshooting

Every failure is a typed error carrying an actionable remediation. The message tells you what to do.

Symptom Kind Fix
Unknown environment "x". Configured: s1. unknown_env Set CCV2_LOGS_HOST_X. The message lists what is configured — a typo in the env name is the usual cause
No session file at ... missing_session Run node scripts/extract-cookie.mjs <env>. If the file exists but is not found, CCV2_SESSION_DIR is not pointing at the clone
Session for "s1" looks expired (HTTP 401) expired_session Reload the dashboard tab in the dedicated Chrome window to refresh SSO, re-run the extraction script. No restart needed — the cookie is re-read per request. Also raised when the upstream returns an HTML login page with a 200 status
offset (9990) + limit (50) exceeds the maximum result window (10000) invalid_argument Deep pagination is capped by the index's max_result_window. Narrow the time range or add a query/aspect filter instead of paging further
No document found for timestamp "..." on pod "..." anchor_not_found The anchor must exist exactly as given. Copy timestamp and pod verbatim from a search_logs result; a rounded timestamp or a recycled pod name will not match
Unexpected response shape: ... unexpected_response Usually a stale cookie or a changed upstream contract. Re-extract the cookie first
Upstream request failed (HTTP 5xx) upstream_http Transient upstream problem, or a payload too large. Retry, then narrow the query

extract-cookie.mjs fails.

  • "Cannot reach Chrome CDP" — Chrome is not running with --remote-debugging-port=9222, or it was already running under a different profile and reused that instance. Quit Chrome fully and relaunch with the flag.
  • "No Cloud Logging tab open" — the dashboard tab was closed or navigated away. Reopen the environment's Logging endpoint.
  • "No cookies for ..." — SSO did not complete. Finish the login (including 2FA) in that window first.

Development

npm test          # vitest run, 77 unit tests
npm run test:watch
npm run build     # tsc -> dist/

The suite is fully offline: fetch and filesystem access are injected through ClientDeps, and every fixture is synthetic.

Live end-to-end suite. test/live/s1.e2e.test.ts exercises the real dashboard and is skipped unless explicitly enabled. It needs a valid cookie for the target environment:

CCV2_LIVE_E2E=1 CCV2_LOGS_HOST_S1=<host> npx vitest run test/live/s1.e2e.test.ts

Architecture

Ports and adapters. The dependency direction is inward, and everything except client/ is pure.

Layer Files Responsibility
src/index.ts 1 Bootstrap only: build client, register tools, connect stdio
src/tools/ 5 MCP adapters — zod schemas, MCP result mapping. No log-domain logic
src/query/ 4 Pure OpenSearch DSL builders and field constants
src/parse/ 4 Pure response parsing — envelope unwrapping, hits, aggregations, context merge
src/config/ 1 env -> host + session file path, pure over an injected ProcessEnv
src/client/ 2 The only I/O in the codebase: one fetch, one readFileSync
src/errors.ts 1 Typed error taxonomy, one factory per kind

Two rules keep this honest, and both are worth preserving in contributions:

  1. I/O lives only in client/. If a new feature needs the network or the filesystem, it goes through LogsClient or gets its own injectable boundary — it does not get added to query/ or parse/.
  2. Every error is a CcvLogsError with a remediation. An error the reader cannot act on is a bug.

See CONTRIBUTING.md for conventions and PR expectations.

License

MIT © 2026 Benja Fernández

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选