cms-mcp
cms-mcp
An MCP (Model Context Protocol) server that reproduces the CMS calls made by the CoreNeo Eclipse plugin's New Query / Service / Exception / Screen / ESB actions — generating OIDs / exception ids and checking definition-name availability — implemented purely in Node.js.
It talks to the CMS the same way the plugin's CmsHttpClient does: Spring
HttpInvoker over HTTP with HTTP Basic auth. Because HttpInvoker uses Java's
native binary serialization, this project ships a small, focused implementation of
the Java Object Serialization Stream Protocol (src/java-serialization.js).
Tools
Tool | CMS method | Notes |
|
| Returns the OID as a string (Java |
|
| Used by the New Exception action. |
|
| New FBML/XAML Screen wizard. Returns |
|
| Lists components; needed to pick a component for name checks / screen names. |
|
| The wizard's "Check Name" button. Supports |
How check_definition_name works (capture-replay)
The existsXxxDefinition methods take a CmsNeoComponent object argument. Instead
of hand-modelling that entity's serialized form (fragile: field order + every
serialVersionUID), the client first calls getComponentList(), captures the
real serialized component objects the server sends, and replays the chosen one
as the argument — reusing the server's own class descriptor. So name checks stay
correct even if the entity changes shape, as long as getComponentList works.
ESB name checks
type: "esb" maps to existsDadDefinition(component, name, GLOBAL_SCOPE, code) —
the same call NewESBConfigurationPage makes. Pass configType
(Map/GroovyWsdl/GroovyRest/Xslt/Uddi); its code (1–5) becomes the
definition type. Notes matching the plugin:
Uddihas no uniqueness check → always reportsexists:falsewithcheckedOnServer:false(no server call).ESB scope is always
GLOBAL_SCOPE(name unique across all components).ESB names are not normalized (kept as-is), and the stored name includes the file extension — e.g. check
EXT_SWIFT.cfg, notEXT_SWIFT.
Configuration
By default the server reads the same files the Eclipse plugin uses — no env vars needed:
File | Values read |
|
|
|
|
cmsPassword is AES-128/ECB/PKCS5 encrypted with the fixed key from
AESConverter — the server decrypts it in memory exactly like the plugin does.
Overrides (all optional)
Environment variables override the files when set:
Var | Meaning |
| Full remoting URL. |
| Build a URL when no |
| Plain (already-decrypted) credentials. |
| Default platform for |
| Per-call timeout (default 60000). |
|
|
| Point at specific config files (e.g. |
On startup the server logs (to stderr) the resolved target, user, platform, and which file each value came from.
Register in Claude Code
Nothing extra needed — it picks up ~/.symphony automatically:
claude mcp add cms -- node C:/workspaces/cms/cms-mcp/src/index.jsTo target a different environment file:
claude mcp add cms --env CMS_CONFIG_XML=C:/Users/you/.symphony/cms_eclipse_config_kaspi.xml \
-- node C:/workspaces/cms/cms-mcp/src/index.jsOr in your MCP client config (.mcp.json / client settings):
{
"mcpServers": {
"cms": {
"command": "node",
"args": ["C:/workspaces/cms/cms-mcp/src/index.js"]
}
}
}Run / test
npm test # offline unit + wire + MCP-handshake tests (no live CMS needed)
npm start # start the stdio server (reads ~/.symphony by default)Test against the live CMS
scripts/live-check.mjs exercises the real calls directly (reads ~/.symphony, no MCP
client needed):
node scripts/live-check.mjs # generate_oid + list_components
node scripts/live-check.mjs oid # generate_oid
node scripts/live-check.mjs exid # get_next_exception_id
node scripts/live-check.mjs screenname "<component>" # generate_screen_name
node scripts/live-check.mjs components # list_components
node scripts/live-check.mjs check query "<component>" "<NAME>" # check_definition_name
node scripts/live-check.mjs esbcheck Map "<component>" "<NAME.cfg>" # ESB name checkDebugging aids: scripts/dump-response.mjs <method> writes a raw serialized
response to tmp/<method>.bin; scripts/parse-bin.mjs <file> parses it and reports
where (if anywhere) deserialization derails.
Status
Verified end-to-end against the live CMS: generate_oid, get_next_exception_id,
list_components (720 components), and check_definition_name for
query/service/region/popup and esb (correct true/false both ways).
The full getComponentList response also re-serializes byte-for-byte identically,
which proves the codec is faithful to the JVM for the real CmsNeoComponent graph.
Notes / gotchas
Component descriptors are captured from
getComponentList, not constructed, so they can't drift. Constructed descriptors that must stay correct live insrc/http-invoker.js(RemoteInvocation) andsrc/java-serialization.js(java.lang.String, and the enum descriptors).Enum gotcha (already handled): the
java.lang.Enumsuper descriptor must be flaggedSC_ENUMtoo (0x12), else the server throwsInvalidClassException: cannot bind non-enum descriptor to an enum class.Field-type signature strings in a class descriptor may be back-references, so the reader resolves them via
readContent(not a fresh string read).
Layout
src/
java-serialization.js JOSP reader + writer (handles, classdesc, enum, array, block-data)
http-invoker.js RemoteInvocation build + POST (Basic auth) + RemoteInvocationResult parse
values.js node -> JS conversions; entity field / collection extraction
cms-client.js generateOid, getNextExceptionId, getComponentList, existsDefinition
symphony-config.js reads ~/.symphony config xml + properties; AES password decrypt
index.js MCP server + tool definitions
test/ serialization / wire / mcp-handshake tests