diff --git a/.changeset/shaggy-pots-kneel.md b/.changeset/shaggy-pots-kneel.md new file mode 100644 index 00000000000..fa03c397aec --- /dev/null +++ b/.changeset/shaggy-pots-kneel.md @@ -0,0 +1,5 @@ +--- +'@sap-ux/fiori-mcp-server': patch +--- + +feat: add Claude Code plugin support diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index b4f98ea8f46..b3bd495fa9f 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -149,9 +149,9 @@ jobs: run: | echo ::set-output name=changes::$(pnpm ci:version 2>&1 | grep -q 'No unreleased changesets found' && echo 'false' || echo 'true') git status - - name: Sync server.json version + - name: Sync MCP manifest versions if: steps.changesetVersion.outputs.changes == 'true' - run: node packages/fiori-mcp-server/scripts/sync-mcp-server-json.js + run: node packages/fiori-mcp-server/scripts/sync-mcp-manifests.js - name: Commit and push changes if: steps.changesetVersion.outputs.changes == 'true' run: | diff --git a/packages/fiori-mcp-server/.claude-plugin/plugin.json b/packages/fiori-mcp-server/.claude-plugin/plugin.json new file mode 100644 index 00000000000..577fd3410fc --- /dev/null +++ b/packages/fiori-mcp-server/.claude-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "fiori-mcp-server", + "version": "0.6.49", + "description": "MCP server for SAP Fiori development tools for Claude Code. Build and modify SAP Fiori applications with AI assistance.", + "author": { + "name": "SAP SE", + "url": "https://sap.com/" + }, + "homepage": "https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server", + "repository": "https://github.com/SAP/open-ux-tools", + "license": "Apache-2.0", + "keywords": ["sap", "fiori", "ui5", "cap", "mcp", "fiori-elements"] +} diff --git a/packages/fiori-mcp-server/.mcp.json b/packages/fiori-mcp-server/.mcp.json new file mode 100644 index 00000000000..445f448c3a1 --- /dev/null +++ b/packages/fiori-mcp-server/.mcp.json @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "fiori-mcp": { + "type": "stdio", + "command": "npx", + "args": ["--yes", "@sap-ux/fiori-mcp-server@latest", "fiori-mcp"] + } + } +} diff --git a/packages/fiori-mcp-server/AGENTS.md b/packages/fiori-mcp-server/AGENTS.md new file mode 100644 index 00000000000..e042221f3bd --- /dev/null +++ b/packages/fiori-mcp-server/AGENTS.md @@ -0,0 +1,44 @@ +# AI Agent Guidelines for `@sap-ux/fiori-mcp-server` + +This document describes the purpose of the key configuration files in this package that relate to MCP server distribution and plugin registries. + +## Configuration Files + +### `server.json` + +This file is the **MCP server registry manifest**. It conforms to the [MCP server schema](https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json) and is used to list this server in MCP server registries (e.g., the official MCP server registry at `registry.modelcontextprotocol.io`). + +Key fields: +- `name` — Unique server identifier in reverse-domain notation (`io.github.SAP/fiori-mcp-server`) +- `description` — Short description shown in registry listings +- `repository` — Points to the GitHub source repository and subfolder +- `version` — Must be kept in sync with `package.json` +- `packages[].environmentVariables` — Documents environment variables users can configure (e.g., `LOG_LEVEL`, `SAP_UX_FIORI_TOOLS_DISABLE_TELEMETRY`) + +> **Important:** Do not manually update `version` in `server.json` or `.claude-plugin/plugin.json`. The `scripts/sync-mcp-manifests.js` script runs automatically in the CI/CD pipeline (`version` job in `pipeline.yml`) after changesets bump `package.json`, and keeps all three files in sync. + +### `.claude-plugin/plugin.json` + +This file is the **Claude Code plugin manifest**. It registers this MCP server as a plugin in the [Claude Code plugin registry](https://code.claude.com/docs/en/plugins), allowing users to discover and install it from within Claude Code. + +Key fields: +- `name` — Plugin identifier shown in the Claude Code registry +- `description` — Brief explanation of what the plugin does (shown to users in registry) +- `author` — Organization name and URL +- `homepage` — URL to the package folder (documentation and README) +- `repository` — URL to the source code repository root +- `license` — SPDX license identifier +- `keywords` — Discovery tags used for search in the registry + +The `.mcp.json` file at the plugin root is automatically picked up by Claude Code to configure the bundled MCP server when the plugin is enabled. + +### `.mcp.json` + +This file is the **project-scoped MCP server configuration** for Claude Code. It follows the [Claude Code `.mcp.json` format](https://code.claude.com/docs/en/mcp#option-1-exclusive-control-with-managed-mcp-json) and serves two purposes: + +1. **Plugin bundling** — When this package is installed as a Claude Code plugin (via `.claude-plugin/plugin.json`), Claude Code reads `.mcp.json` to automatically start the MCP server. +2. **Local development** — Developers working in this repository can use this file directly to connect the locally published server to Claude Code. + +Key fields: +- `mcpServers.fiori-mcp.type` — Transport type (`stdio` for local process) +- `mcpServers.fiori-mcp.command` / `args` — How to launch the server via `npx` diff --git a/packages/fiori-mcp-server/scripts/sync-mcp-manifests.js b/packages/fiori-mcp-server/scripts/sync-mcp-manifests.js new file mode 100644 index 00000000000..774a6951f49 --- /dev/null +++ b/packages/fiori-mcp-server/scripts/sync-mcp-manifests.js @@ -0,0 +1,59 @@ +#!/usr/bin/env node +// Syncs the version in server.json and .claude-plugin/plugin.json with package.json. +// Called from the version job in pipeline.yml after `changeset version` bumps package.json. + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const pkgPath = path.join(__dirname, '..', 'package.json'); +const serverJsonPath = path.join(__dirname, '..', 'server.json'); +const pluginJsonPath = path.join(__dirname, '..', '.claude-plugin', 'plugin.json'); + +/** + * Reads and parses a JSON file, throwing a clear error if the file is missing or contains invalid JSON. + * @param {string} filePath + * @returns {object} + */ +function readJson(filePath) { + if (!fs.existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + const content = fs.readFileSync(filePath, 'utf8'); + try { + return JSON.parse(content); + } catch (e) { + throw new Error(`Invalid JSON in ${filePath}: ${e.message}`); + } +} + +try { + const pkg = readJson(pkgPath); + const serverJson = readJson(serverJsonPath); + const pluginJson = readJson(pluginJsonPath); + + const { version } = pkg; + + // Update top-level version + serverJson.version = version; + + // Update version in all package entries + if (Array.isArray(serverJson.packages)) { + for (const packageEntry of serverJson.packages) { + packageEntry.version = version; + } + } + + // Update version in Claude Code plugin manifest + pluginJson.version = version; + + fs.writeFileSync(serverJsonPath, JSON.stringify(serverJson, null, 4) + '\n'); + console.log(`Updated server.json to version ${version}`); + + fs.writeFileSync(pluginJsonPath, JSON.stringify(pluginJson, null, 4) + '\n'); + console.log(`Updated .claude-plugin/plugin.json to version ${version}`); +} catch (e) { + console.error(`Error: ${e.message}`); + process.exit(1); +} diff --git a/packages/fiori-mcp-server/scripts/sync-mcp-server-json.js b/packages/fiori-mcp-server/scripts/sync-mcp-server-json.js deleted file mode 100644 index 42b5e21c6fa..00000000000 --- a/packages/fiori-mcp-server/scripts/sync-mcp-server-json.js +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env node -// Syncs the version in packages/fiori-mcp-server/server.json with its package.json. -// Called from the version job in pipeline.yml after `changeset version` bumps package.json. - -'use strict'; - -const fs = require('fs'); -const path = require('path'); - -const pkgPath = path.join(__dirname, '..', 'package.json'); -const serverJsonPath = path.join(__dirname, '..', 'server.json'); - -const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); -const serverJson = JSON.parse(fs.readFileSync(serverJsonPath, 'utf8')); - -const { version } = pkg; - -// Update top-level version -serverJson.version = version; - -// Update version inside packages[0] (npm package entry) -if (Array.isArray(serverJson.packages) && serverJson.packages.length > 0) { - serverJson.packages[0].version = version; -} - -fs.writeFileSync(serverJsonPath, JSON.stringify(serverJson, null, 4) + '\n'); -console.log(`Updated server.json to version ${version}`);