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.
README
ccv2-logs-mcp
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
npm install && npm run build- Export
CCV2_LOGS_HOST_<ENV>for each environment you want to reach. - Extract a session cookie:
node scripts/extract-cookie.mjs s1 - 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_DIRto the absolute path of the clone.scripts/extract-cookie.mjsalways 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. SettingCCV2_SESSION_DIRexplicitly 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:
-
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" -
In that window, open the environment's Logging endpoint from SAP Cloud Portal and complete SSO manually, including 2FA. Leave the tab open.
-
Extract the cookie into
.ccv2-session-<env>:node scripts/extract-cookie.mjs s1The script finds the Cloud Logging tab over CDP, reads its cookies, and writes them to
.ccv2-session-s1with mode0600. 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'sPATH, which may point at an older Node than the one you installed with (this package requires Node >= 20.19). Find yours withcommand -v nodein the shell wherenpm installsucceeded (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 usermakes the server available in every project. Without it,claude mcp addregisters 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:
- I/O lives only in
client/. If a new feature needs the network or the filesystem, it goes throughLogsClientor gets its own injectable boundary — it does not get added toquery/orparse/. - Every error is a
CcvLogsErrorwith 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
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。