ccv2-logs-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ccv2-logs-mcpany errors on s1 in the last hour?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 buildExport
CCV2_LOGS_HOST_<ENV>for each environment you want to reach.Extract a session cookie:
node scripts/extract-cookie.mjs s1Register the server with Claude Code and ask: "any errors on s1 in the last hour?"
Full detail for each step is below.
Related MCP server: OpenSearch Logs MCP Server
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 |
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 buildThe 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 |
| yes, one per environment | — | Dashboards hostname for |
| recommended | process working directory | Directory holding the |
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.jsPass 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 |
| string | — | Environment name, e.g. |
| string | — | Free text over message, |
| string | — |
|
| string | — |
|
| string |
| Start of range |
| string |
| End of range |
| integer 1–500 |
| Lines to return |
| integer >= 0 |
| Lines to skip. |
// 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 |
| string | — | Environment name |
| string |
| Start of range |
| string |
| End of range |
| string | — | Optional aspect filter |
| integer 1–100 |
| 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 |
| string | — | Environment name |
| string | — | Anchor timestamp, ISO 8601. Copy it from a |
| string | — | Pod name. Copy it from a |
| integer 0–200 |
| Lines before the anchor |
| integer 0–200 |
| 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 |
Cookies are never bundled | The |
Debug port exposure | The extraction script only talks to |
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 |
|
| Set |
|
| Run |
|
| 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 |
|
| Deep pagination is capped by the index's |
|
| The anchor must exist exactly as given. Copy |
|
| Usually a stale cookie or a changed upstream contract. Re-extract the cookie first |
|
| 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.tsArchitecture
Ports and adapters. The dependency direction is inward, and everything except client/ is pure.
Layer | Files | Responsibility |
| 1 | Bootstrap only: build client, register tools, connect stdio |
| 5 | MCP adapters — zod schemas, MCP result mapping. No log-domain logic |
| 4 | Pure OpenSearch DSL builders and field constants |
| 4 | Pure response parsing — envelope unwrapping, hits, aggregations, context merge |
| 1 |
|
| 2 | The only I/O in the codebase: one |
| 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
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Tools
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/bfernandez2-seidor/ccv2-logs-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server