Skip to main content
Glama
bfernandez2-seidor

ccv2-logs-mcp

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.

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 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

Install Server
A
license - permissive license
A
quality
A
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

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