You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2026/05/16 09:30:16 UTC

(dubbo-go-pixiu) branch develop updated: feat: add Pixiu skills (#921)

This is an automated email from the ASF dual-hosted git repository.

AlexStocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git


The following commit(s) were added to refs/heads/develop by this push:
     new dd62a985 feat: add Pixiu skills (#921)
dd62a985 is described below

commit dd62a985a0706c1ff4aa0a2adb632fd256f328ba
Author: wm_03 <31...@qq.com>
AuthorDate: Sat May 16 17:30:11 2026 +0800

    feat: add Pixiu skills (#921)
    
    * feat: add Pixiu skills
---
 skills/pixiu-filter-author/SKILL.md   | 359 ++++++++++++++++++++++++++++++
 skills/pixiu-http-to-dubbo/SKILL.md   | 399 ++++++++++++++++++++++++++++++++++
 skills/pixiu-llm-gateway/SKILL.md     | 293 +++++++++++++++++++++++++
 skills/pixiu-mcp-integration/SKILL.md | 231 ++++++++++++++++++++
 4 files changed, 1282 insertions(+)

diff --git a/skills/pixiu-filter-author/SKILL.md b/skills/pixiu-filter-author/SKILL.md
new file mode 100644
index 00000000..83220104
--- /dev/null
+++ b/skills/pixiu-filter-author/SKILL.md
@@ -0,0 +1,359 @@
+---
+name: pixiu-filter-author
+description: |
+  Create a new HTTP or Network filter for Apache dubbo-go-pixiu. Use whenever
+  the user wants to add a custom filter, extend gateway per-request behavior,
+  implement a new auth/logging/transformation/proxy filter, or mentions
+  "pixiu filter", "dgp.filter", "http_filters", "filter chain", "custom filter",
+  "extend pixiu", or is porting a filter from Envoy/Kong/APISIX to pixiu.
+  Strongly prefer this skill over ad-hoc generation even when the user does
+  not say the word "skill" — getting the SPI registration right is the single
+  biggest source of "my filter does not run" bug reports.
+allowed-tools: [Read, Grep, Glob, Edit, Write, Bash]
+metadata:
+  version: "0.1.2"
+  domain: extension
+  scope: implementation
+  triggers: ["filter", "pixiu filter", "dgp.filter", "http_filters", "filter chain", "custom filter", "extend pixiu"]
+  pixiu_min_version: "0.6.0"
+  role: specialist
+---
+
+# pixiu-filter-author — Authoring HTTP / Network Filters
+
+dubbo-go-pixiu's extension surface is Envoy-style: every filter is a Go
+package that registers a `Plugin` in `init()`, and a central file
+(`pkg/pluginregistry/registry.go`) blank-imports all such packages to
+activate them. A single missing blank import is the most common reason a
+newly written filter "does nothing" — so this skill treats registration as
+a first-class concern, not an afterthought.
+
+## When to Use
+
+Use this skill when the user wants to:
+- Add a new HTTP filter (auth, logging, header rewriting, rate limiting,
+  request transformation, metric emission, etc.).
+- Add a new Network filter (e.g. a custom connection manager).
+- Port a filter from Envoy / Kong / APISIX onto pixiu.
+- Fix an existing filter that "isn't running" — start at Step 5 (blank
+  import) and Step 3 (phase).
+
+**Do NOT use this skill for:**
+- Editing the yaml that *uses* an existing filter → that is plain config
+  work. Only include a minimal mounting snippet when you create a new
+  filter in this skill.
+- Mounting an already-registered filter such as CORS, JWT, OPA, MCP,
+  LLM proxy, or Dubbo proxy. In that case do not create new filter code;
+  do not touch `pkg/pluginregistry/registry.go`; answer with the
+  smallest `http_filters` snippet and state that the filter already
+  exists.
+  If pressured to add a package anyway, explicitly answer:
+  "this is already-registered; do not create new filter code; do not
+  touch `pkg/pluginregistry/registry.go`."
+- Adding a registry adapter (Nacos, ZK, Consul), a new protocol
+  listener (WebSocket, MQTT), or a custom load-balancing algorithm —
+  these are different SPI hubs and out of scope for this skill. Point
+  the user to the relevant interface file
+  (`pkg/common/extension/adapter/adapter.go`,
+  `pkg/listener/listener.go`, or
+  `pkg/cluster/loadbalancer/`) and let them work directly.
+
+## Prerequisites
+
+- pixiu ≥ 0.6.0. Verify with `grep dubbo-go-pixiu go.mod` in Step 0.
+- The user should be inside a clone of `github.com/apache/dubbo-go-pixiu`
+  (or a fork). If they are not, stop and ask — this skill edits repo files.
+
+## Steps
+
+### Step 0 — Verify Context (ALWAYS FIRST, no exceptions)
+
+Before writing a single line of code, read the current shape of three
+files. pixiu's SPI is stable but not frozen, and your instinct about
+these interfaces can be one minor version stale:
+
+1. `pkg/common/extension/filter/filter.go` — the four interfaces
+   (`HttpFilterPlugin`, `HttpFilterFactory`, `HttpDecodeFilter`,
+   `HttpEncodeFilter`), plus `NetworkFilterPlugin` / `NetworkFilter`.
+   Read signatures, especially `PrepareFilterChain`.
+2. `pkg/pluginregistry/registry.go` — the blank-import list you will
+   extend in Step 5. Note the alphabetical order within groups.
+3. `pkg/filter/cors/cors.go` — the shortest idiomatic HTTP filter in the
+   tree. A single file, all four types (`Plugin`, `FilterFactory`,
+   `Filter`, `Config`) declared together. Note: `cors` ships **without**
+   a `_test.go` — that matches roughly half of the existing filter
+   packages. See Step 7 for the project's actual convention.
+
+Then `ls pkg/filter/` and `ls pkg/filter/http/` to see the existing
+directory conventions. Do not assume — layouts evolve (e.g. `cors` lives
+at `pkg/filter/cors/`, while proxy filters live at
+`pkg/filter/http/httpproxy/`, `pkg/filter/http/dubboproxy/`).
+
+If an interface detail is unclear, stay in the source file instead of
+relying on memory.
+
+### Step 1 — Clarify the Filter Shape (STOP and ask)
+
+Do not write code until the user has answered, in plain words:
+
+1. **Phase**: Decode (request, before upstream), Encode (response, after
+   upstream), or both?
+2. **Kind**: the string that identifies the filter in yaml. It MUST start
+   with `dgp.filter.http.` for HTTP filters or
+   match Pixiu's current Network filter constants, usually
+   `dgp.filter.network.`. The built-in HTTP connection manager is the
+   special network-filter Kind `dgp.filter.httpconnectionmanager`.
+   Suggest a name after checking `pkg/common/constant/key.go`; confirm
+   the user is happy.
+3. **Config fields**: what yaml keys does the user want? (`allow_origin`,
+   `max_request_bytes`, etc.)
+4. **Where to put the package**: the convention is
+   `pkg/filter/<name>/` for ordinary filters and
+   `pkg/filter/http/<name>/` for *proxy-style* filters (the ones that
+   actually call upstream). If unsure, ask — getting this right once
+   beats moving files later.
+
+If the user says "just write something reasonable", pick a minimal
+request-logging filter as the placeholder and be explicit about the
+assumptions in a short summary before coding.
+
+### Step 2 — Create the Package Skeleton
+
+Create the directory and initial files. Two shapes are idiomatic in the
+existing tree:
+
+- **Single file** (cors, csrf, jwt, header, host, tracing): everything
+  lives in `<name>.go`. This is the dominant pattern for non-proxy
+  filters — about 20 of the existing filter packages follow it.
+- **Split files** (network/dubboproxy, mcp/mcpserver, ai/kvcache, etc.):
+  `plugin.go`, `filter.go`, `config.go`. Use this only when the package
+  has more than ~200 LOC or several distinct types worth separating.
+
+Test files (`*_test.go`) are **not** part of either default shape —
+they are added on a per-filter basis when the logic warrants it. See
+Step 7.
+
+Create the initial files directly, using the closest in-tree filter as
+the pattern. Keep the first version minimal: the four interface types,
+`Kind`, `init()` registration, `Config`, and empty `Decode` / `Encode`
+methods as appropriate. The user still owns every Config field.
+
+### Step 3 — Implement the Four Interfaces
+
+The four types for an HTTP filter, in order:
+
+```go
+type Plugin struct{}
+func (p *Plugin) Kind() string { return Kind }
+func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
+    return &FilterFactory{cfg: &Config{}}, nil
+}
+
+type FilterFactory struct{ cfg *Config }
+func (f *FilterFactory) Config() any { return f.cfg }
+func (f *FilterFactory) Apply() error { /* validate / defaults */ return nil }
+func (f *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
+    inst := &Filter{cfg: f.cfg.DeepCopy()} // or a shallow copy of the pieces you need
+    chain.AppendDecodeFilters(inst)        // or AppendEncodeFilters, or both
+    return nil
+}
+
+type Filter struct{ cfg *Config }
+func (f *Filter) Decode(ctx *http.HttpContext) filter.FilterStatus { return filter.Continue }
+// and/or:
+func (f *Filter) Encode(ctx *http.HttpContext) filter.FilterStatus { return filter.Continue }
+```
+
+A few subtle rules the interface alone does not enforce:
+
+- **Do not reuse `factory.cfg` directly in the Filter instance.** The
+  factory's config pointer can be hot-reloaded at runtime. Copy the
+  fields you need into the Filter when `PrepareFilterChain` runs. The
+  CORS filter's `factory.cfg.DeepCopy()` pattern is the canonical
+  reference.
+- **Decode vs Encode is about the phase, not the direction.**  Mutating
+  `ctx.TargetResp` (the outbound body) from a Decode filter is a
+  category error — read `pkg/context/http/` before touching response
+  state.
+- **Response-body mutation is an Encode-only checklist.** For redaction,
+  watermarking, compression, or similar response transforms, inspect
+  existing response-transforming filters and their tests.
+  Operate on `ctx.TargetResp` only after confirming the concrete
+  response type. Handle `*client.UnaryResponse` deliberately, pass
+  streaming responses through unless the user explicitly asked for
+  streaming support, gate by `Content-Type` when the behavior is textual,
+  and write table-driven tests for empty body, unsupported content type,
+  configured/default values, and invalid config.
+
+For Network filters, the interface surface is larger
+(`ServeHTTP`, `OnData`, `OnTripleData`, etc.); reuse an existing one
+(`pkg/filter/network/httpconnectionmanager/`) as a template.
+
+### Step 4 — Register in `init()`
+
+```go
+func init() {
+    filter.RegisterHttpFilter(&Plugin{})
+}
+```
+
+For Network filters the call is `filter.RegisterNetworkFilterPlugin(&Plugin{})`.
+
+### Step 5 — Add the Blank Import (THIS is where filters die silently)
+
+Edit `pkg/pluginregistry/registry.go` and insert, in alphabetical order
+within the correct grouping:
+
+```go
+_ "github.com/apache/dubbo-go-pixiu/pkg/filter/<name>"
+```
+
+(Or `.../pkg/filter/http/<name>` if the package lives there.)
+
+If this line is missing, the filter compiles fine, `go test` passes, and
+pixiu boots — but the filter never registers, and yaml using its Kind
+fails with `no filter found for name ...`. This is *the* pixiu rite of
+passage.
+
+In answers, spell this out: package-level tests can pass while the
+gateway binary never imports the package, so the plugin `init()` never
+registers. The missing blank import is still required.
+
+Manually check the new package against `pkg/pluginregistry/registry.go`:
+if the package calls `filter.RegisterHttpFilter` or
+`filter.RegisterNetworkFilterPlugin`, it must be blank-imported there.
+Review the existing grouping and alphabetical order before inserting the
+new line.
+
+If the user asked for a "full workflow", do not stop after writing the
+filter package. The final answer must include either the applied
+`registry.go` edit or a precise patch hunk the user can apply.
+
+### Step 6 — Wire Up Config
+
+Add your filter to `configs/conf.yaml` (or whatever bootstrap the user
+is running), under the `http_filters` list of the
+`dgp.filter.httpconnectionmanager` network filter. Order matters: CORS /
+request auth / tracing normally run before proxy-style filters, while
+response-shaping filters must be placed where their Encode phase will
+see the intended response.
+
+```yaml
+- name: dgp.filter.http.<name>
+  config:
+    <field>: <value>
+```
+
+Do NOT put the filter under `static_resources.listeners[].filter_chains[].filters[]`
+directly — that's the Network filter level. HTTP filters always live
+inside `dgp.filter.httpconnectionmanager`'s `http_filters`.
+
+### Step 7 — Tests (optional; follow the project's actual convention)
+
+Pixiu does **not** have a "every filter must have tests" policy. Decide
+from the behavior you add, then mirror a nearby filter with comparable
+complexity:
+
+- **Skip the test** when your filter is a thin config-driven header
+  rewriter, an auth check that delegates to a library, a one-line proxy
+  switch, or otherwise mostly setup — matches cors / csrf / jwt /
+  httpproxy.
+- **Add a table-driven test** when your filter has branching logic,
+  state transitions, or response-body transformation — matches
+  sentinel/ratelimit / opa / accesslog.
+
+If you choose to test, copy the style of nearby table-driven tests in
+`pkg/filter/`. If you choose not to test, do not generate an empty stub
+`*_test.go` — empty test files are noise and do not match repo style.
+
+When in doubt, ask the user. Either answer is consistent with the
+project; pick deliberately.
+
+### Step 8 — Verify End-to-End
+
+- `go build ./...` from repo root must succeed.
+- If you wrote tests: `go test ./pkg/filter/<name>/...` must pass.
+- If the user has the gateway binary, `./dubbo-go-pixiu gateway start -c
+  configs/conf.yaml` should log the filter's Kind among the registered
+  filters at boot.
+- A `curl` against the listener port should exhibit the new behavior.
+
+For full-workflow answers, emit a compact delivery checklist:
+
+- Filter package path and the `Kind` string.
+- `pkg/pluginregistry/registry.go` blank-import patch or applied edit.
+- Minimal `http_filters` yaml snippet mounting the new filter.
+- Test decision: either real test files and commands, or a short reason
+  why no `_test.go` is consistent with nearby pixiu filters.
+- Verification commands run or, if not runnable, the exact commands the
+  user should run.
+
+## Cross-Cutting Rules
+
+### Always
+
+- Name the `Kind` constant with the correct prefix. HTTP filters use
+  `dgp.filter.http.*`; Network filters must match the current constants
+  in `pkg/common/constant/key.go` such as `dgp.filter.network.*` or the
+  special `dgp.filter.httpconnectionmanager`. Put it in a `const` at
+  the top of the package.
+- Use the project's logger: `import ".../pkg/logger"` and
+  `logger.Infof(...)`. Never `fmt.Println`, never stdlib `log`.
+- Deep-copy config into the per-request `Filter` instance — the factory
+  config is live and can change under you.
+- Keep imports in three alphabetically ordered groups (stdlib, third
+  party, internal) — this is a project-wide convention and the CI
+  linter enforces it.
+- Match the surrounding package style on testing: look at neighbors of
+  comparable complexity (cors / jwt for simple, opa / sentinel for
+  branching) and follow their pattern.
+- In full-workflow mode, always produce registry and config artifacts.
+  A filter package alone is incomplete even when it compiles.
+
+### Never
+
+- Expose internal error details in HTTP responses. Return generic
+  client-facing errors and log full diagnostics server-side only.
+- Skip the blank import. This is the #1 filter bug. If something does
+  not seem to register, re-check this before anything else.
+- Accept `dgp.filter.networkfilter.*`. That prefix is invalid in current
+  Pixiu; HTTP filters use `dgp.filter.http.*`, and Network filter Kinds
+  must be checked against `pkg/common/constant/key.go`.
+- Mutate `ctx.TargetResp` from a Decode filter. Use Encode.
+- Rewrite response streams unless the user explicitly requested
+  streaming support and you have designed backpressure/lifetime handling.
+- Launch a goroutine inside Decode/Encode without a parent context and
+  timeout — it will outlive the request.
+- Return `nil, error` from `CreateFilterFactory` for a non-fatal config
+  problem — instead return a zero-valued factory and log a warning; this
+  keeps pixiu bootable.
+- Generate an empty stub `*_test.go` "to be filled in later". Either
+  write a real test or omit the file.
+
+## Common Pitfalls
+
+1. **"My filter doesn't run" = missing blank import.** 90% of the time.
+   Check `pkg/pluginregistry/registry.go` first.
+2. **Phase confusion.** Auth → Decode; response shaping → Encode.
+   Filters are invoked Decode forward, Encode reversed — draw it out
+   once and the rest follows.
+3. **`ctx.Abort` vs `ctx.Next`.** `Abort` stops subsequent Decode
+   filters, but Encode filters of already-entered filters still run.
+4. **Per-request state in the factory.** The factory is shared across
+   all requests on a listener; per-request state belongs in the Filter
+   struct created by `PrepareFilterChain`.
+5. **Dubbo-aware filters running too early.** Anything that needs the
+   request's Dubbo parameters parsed must run *after*
+   `dgp.filter.httpconnectionmanager` has done its work — effectively,
+   it must be inside its `http_filters` list.
+6. **Yaml tags omitted on Config struct.** Without
+   `yaml:"field_name" mapstructure:"field_name"`, your field will not
+   bind. Follow CORS's tag pattern exactly.
+
+## Source Files To Read
+
+- `pkg/common/extension/filter/filter.go`
+- `pkg/pluginregistry/registry.go`
+- `pkg/filter/cors/cors.go`
+- Nearby filters under `pkg/filter/` or `pkg/filter/http/` with similar
+  complexity.
diff --git a/skills/pixiu-http-to-dubbo/SKILL.md b/skills/pixiu-http-to-dubbo/SKILL.md
new file mode 100644
index 00000000..b7279bb9
--- /dev/null
+++ b/skills/pixiu-http-to-dubbo/SKILL.md
@@ -0,0 +1,399 @@
+---
+name: pixiu-http-to-dubbo
+description: |
+  Map a REST/HTTP endpoint onto a backend Dubbo service through
+  dubbo-go-pixiu. Use whenever the user wants to "expose Dubbo as HTTP",
+  "REST gateway for Dubbo", "Dubbo generic invoke", configure
+  `api_config.yaml`, `integrationRequest`, `mappingParams`, `mapType`,
+  `opt.types` / `opt.values`, group / version, or says "how do I call
+  my Dubbo service from curl".
+  Use this even when the user only asks to tweak an existing yaml route —
+  the combination of conf.yaml + api_config.yaml is where most "500 from
+  pixiu" reports come from, and this skill encodes the invariants.
+allowed-tools: [Read, Grep, Glob, Edit, Write, Bash]
+metadata:
+  version: "0.1.2"
+  domain: config
+  scope: generate-and-validate
+  triggers: ["http to dubbo", "REST to Dubbo", "Dubbo gateway", "api_config.yaml", "integrationRequest", "Dubbo generic invoke", "mappingParams", "mapType", "opt.types", "expose Dubbo as HTTP"]
+  pixiu_min_version: "0.6.0"
+  role: specialist
+---
+
+# pixiu-http-to-dubbo — Wiring HTTP Clients to Dubbo Backends
+
+pixiu's bread and butter is "let a REST client call a Dubbo service". On
+the surface it's two yaml files; underneath, you are configuring a
+generic Dubbo invoke, and most failures come from the three-way mismatch
+between the client request shape, the Dubbo interface signature, and
+the mapping rules in between.
+
+## Two files, two jobs
+
+- **`conf.yaml`** (bootstrap) — wires the HTTP listener, enables the
+  `dgp.filter.httpconnectionmanager` network filter, and loads the
+  Dubbo registry adapter. You almost always keep this close to the
+  sample; changes are listener port, adapter type, and sometimes
+  cluster definitions.
+  In config answers, explicitly use canonical HCM Kind
+  `dgp.filter.httpconnectionmanager` (never
+  `dgp.filter.http.httpconnectionmanager`) and put
+  `dgp.filter.http.apiconfig` before `dgp.filter.http.dubboproxy` or
+  `dgp.filter.http.httpproxy` inside HCM `http_filters`.
+- **`api_config.yaml`** — the per-endpoint mapping. One
+  `resources[].methods[]` entry per HTTP route. Every field except the
+  path comes from the Dubbo side; every mapping rule comes from the
+  HTTP side.
+
+This skill's job is to keep those two in sync.
+
+## When to Use
+
+Use this skill when the user wants to:
+- Expose an existing Dubbo interface through HTTP (GET/POST/PUT/DELETE).
+- Configure `integrationRequest` for an existing route.
+- Fix a 500 / "generic invoke failed" / "no provider found" error that
+  points at a yaml route.
+- Add parameter-name mapping (query → method param, body field → method
+  param, header → method param).
+- Switch an existing HTTP proxy route to Dubbo backend.
+
+**Do NOT use this skill for:**
+- Writing a new filter in the chain → `pixiu-filter-author`.
+- Adding a new registry adapter for a non-standard service registry —
+  out of scope; the user will need to author the adapter directly
+  against `pkg/common/extension/adapter/adapter.go`.
+- Pure yaml audit with no new HTTP-to-Dubbo route — out of scope. This
+  skill only validates the route/config fragments it generates or edits.
+
+## Prerequisites
+
+- pixiu ≥ 0.6.0.
+- A Dubbo provider running somewhere reachable. The user must know the
+  interface FQCN, method name, Java method signature, and the Dubbo
+  `group` / `version`.
+- Registry mode needs ZooKeeper/Nacos config under
+  `dgp.filter.http.dubboproxy.config.dubboProxyConfig.registries`.
+  Direct mode instead uses `integrationRequest.url` in `api_config.yaml`
+  and must also declare `parameterTypes` and `serialization`.
+
+## Steps
+
+### Step 0 — Verify Context
+
+1. Confirm pixiu version: `grep dubbo-go-pixiu go.mod`.
+2. Locate existing config: typically `configs/conf.yaml` and
+   `configs/api_config.yaml` in the pixiu repo, or user-specified
+   paths.
+3. Open `pkg/config/api_config.go` — that's the Go struct the yaml
+   actually binds to. Current `DubboBackendConfig` includes
+   `parameterTypes` and `serialization`; the legacy spelling
+   `paramTypes` is not the current field name.
+4. Open `pkg/filter/http/remote/dubbo_handler.go`,
+   `pkg/client/dubbo/types.go`, `pkg/client/dubbo/typeconv.go`, and
+   `pkg/client/dubbo/dubbo.go` before editing mappings. Do not cite
+   legacy mapping helpers on branches where they no longer exist.
+5. If the user says "Dubbo direct URL, no registry", use the current
+   direct generic contract: `integrationRequest.url` plus explicit
+   `protocol`, `parameterTypes`, and `serialization`.
+
+### Step 1 — Gather the Required Things (STOP and ask)
+
+You cannot write a valid `integrationRequest` without all applicable
+items:
+
+1. **HTTP method + path**: `POST /api/v1/user`, etc.
+2. **Dubbo interface FQCN**: e.g. `com.example.UserProvider`.
+3. **Dubbo method name**: e.g. `createUser`.
+4. **Java method signature**: e.g.
+   `User createUser(com.example.User user)` or
+   `Page<Order> search(String tenant, OrderQuery q)`. Translate each
+   argument into `parameterTypes` and supported pixiu `mapType` values
+   (`string`, `int`, `long`, `double`, `boolean`, `object`, etc.).
+5. **Dubbo `group` and `version`**: empty strings are allowed but must
+   be explicit.
+6. **Where each parameter comes from** on the HTTP side: `queryStrings.
+   <name>`, `requestBody.<path>`, `headers.<name>`, `uri.<name>`.
+7. **Registry/direct mode**: ZK/Nacos registry, or direct URL. Registry
+   settings live in `conf.yaml`; direct provider address lives in
+   `integrationRequest.url`.
+8. **Direct-call extras**: for direct URL mode, the declared protocol,
+   `parameterTypes`, and `serialization`.
+
+For POJO arguments, also ask whether the HTTP JSON body includes the
+Dubbo/Hessian class discriminator the provider expects, usually a
+`class` field such as `"class": "com.example.User"`. Pixiu can map the
+argument as `object`, but it cannot infer every provider-side POJO FQCN
+from a yaml field that the current config struct ignores.
+
+For primitives, collections, and nested POJOs, stay close to
+`DubboHandler`, `typeconv.go`, and existing `api_config.yaml` examples
+before finalizing `mappingParams`.
+
+If the user says "direct URL", "no registry", or gives a provider
+address such as `10.0.0.8:20880`, keep the Dubbo route as
+`integrationRequest.requestType: dubbo` and put the provider address in
+`integrationRequest.url`. Direct generic invoke also requires
+`parameterTypes` and `serialization`; without them Pixiu returns direct
+generic validation errors before invoking the provider.
+
+Do NOT proceed until the required fields are answered. It is fine to propose
+defaults and ask for confirmation — but **every field must be explicit
+in the final yaml**.
+
+### Step 2 — Shape the `api_config.yaml` entry
+
+The template:
+
+```yaml
+name: pixiu
+description: <what this gateway does>
+resources:
+  - path: /api/v1/user
+    type: restful
+    description: create a user
+    methods:
+      - httpVerb: POST
+        enable: true
+        timeout: 1000ms
+        inboundRequest:
+          requestType: http
+          # describe the HTTP shape the client will send:
+          headers:
+            - name: X-Request-Id
+              required: false
+          queryStrings: []
+          requestBody:
+            - contentType: application/json
+              schema: user
+              required: true
+        integrationRequest:
+          requestType: dubbo
+          # Dubbo coordinates:
+          applicationName: pixiu
+          group: ""
+          version: "1.0.0"
+          interface: com.example.UserProvider
+          method: createUser
+          parameterTypes:
+            - com.example.User
+          # How to pull each Dubbo method argument from the HTTP request:
+          mappingParams:
+            - name: requestBody
+              mapTo: "0"
+              mapType: object
+```
+
+Rules the template hides:
+
+- `requestType` **must** be `dubbo` for Dubbo routes. `http` is the
+  pass-through mode; different code path.
+- `requestType: triple` uses the same outbound builder, but direct
+  Triple generic invoke requires the provider to expose generic `$invoke`;
+  IDL-only Triple handlers may return `404 Not Found`.
+- `mapTo` is either the **index** of the Dubbo method parameter
+  (`mapTo: "0"` is the first argument) or one of the supported
+  `opt.*` targets such as `opt.types`, `opt.values`, `opt.group`,
+  `opt.version`, `opt.interface`, or `opt.method`. `opt.application`
+  is deprecated in the current handler.
+- `parameterTypes` is the preferred explicit Java signature. In
+  registry mode, `opt.types` can still provide the signature when
+  `parameterTypes` is omitted. In direct mode, `parameterTypes` is
+  required.
+- `mapType` is the conversion hint consumed by pixiu's Dubbo type
+  conversion path. Supported values come from `constant.JTypeMapper`: `string`,
+  `java.lang.String`, `char`, `short`, `int`, `long`, `float`,
+  `double`, `boolean`, `java.util.Date`, `date`, `object`, and
+  `java.lang.Object`. Use `object` / `java.lang.Object` for POJO or map
+  payloads, and include provider-required class metadata in the body
+  when the Dubbo serializer needs it.
+- `paramTypes` is not the current field name. Use `parameterTypes`.
+- Registry mode gets provider discovery from
+  `dubboProxyConfig.registries`. Direct mode gets the provider address
+  from `integrationRequest.url`; do not model it as a static cluster-only
+  path unless the target branch proves that older direct filter is in use.
+
+For dynamic/default generic routes where the HTTP client explicitly
+sends the type list and value list, map body fields to `opt.types` and
+`opt.values` instead of using top-level yaml fields:
+
+```yaml
+mappingParams:
+  - name: requestBody.types
+    mapTo: opt.types
+  - name: requestBody.values
+    mapTo: opt.values
+```
+
+When the user pastes legacy `paramTypes`, `groupType`, or old
+`types/values` examples, translate the intent into current
+`parameterTypes`, `mappingParams`, and `mapType` rules. Do not preserve
+unknown fields just because the user supplied them; current
+`IntegrationRequest` silently ignores fields not present in
+`pkg/config/api_config.go`.
+
+### Step 3 — Update `conf.yaml` if needed
+
+You usually only touch `conf.yaml` when:
+
+- This is the first registry-backed Dubbo route (need
+  `dgp.filter.http.dubboproxy.config.dubboProxyConfig.registries`, and
+  often the dynamic `dgp.adapter.dubboregistrycenter` if API definitions
+  come from a registry).
+- A new listener port or host is needed.
+- The user wants direct/no-registry Dubbo access. In that case
+  `api_config.yaml` must carry `integrationRequest.url`,
+  `parameterTypes`, and `serialization`; `conf.yaml` still needs the
+  listener/filter chain, but not a static Dubbo cluster endpoint for
+  that direct provider.
+
+The `dgp.filter.http.dubboproxy` registry block (ZK example, inside HCM
+`http_filters`):
+
+```yaml
+- name: dgp.filter.http.dubboproxy
+  config:
+    dubboProxyConfig:
+      registries:
+        zk:
+          protocol: zookeeper
+          timeout: 3s
+          address: 127.0.0.1:2181
+          username: ""
+          password: ""
+      timeout_config:
+        connect_timeout: 5s
+        request_timeout: 5s
+```
+
+Add `dgp.adapter.dubboregistrycenter` only when API definitions are
+dynamically discovered from a Dubbo registry, not for every static
+`api_config.yaml` route.
+
+For direct/no-registry cases, omit the registry adapter and put the
+provider address in `api_config.yaml`:
+
+```yaml
+integrationRequest:
+  requestType: dubbo
+  url: dubbo://10.0.0.8:20880
+  protocol: dubbo
+  serialization: hessian2
+  interface: com.example.UserProvider
+  method: getUser
+  group: ""
+  version: "1.0.0"
+  parameterTypes:
+    - java.lang.String
+  mappingParams:
+    - name: queryStrings.name
+      mapTo: "0"
+      mapType: string
+```
+
+If the URL omits a scheme, `protocol` must still be set so Pixiu can
+construct the direct Dubbo reference.
+
+And the HTTP listener MUST have `dgp.filter.http.apiconfig` in its
+`http_filters` list, followed by `dgp.filter.http.dubboproxy` (or
+`dgp.filter.http.httpproxy` if some routes are pass-through). Filter
+order matters — compare against the current config structs and examples.
+
+### Step 4 — Validate
+
+Before booting Pixiu, inspect the generated config directly:
+
+1. Parse the yaml with an available local parser or by loading it through
+   Pixiu's config path.
+2. Check the current API config structs and examples for required keys,
+   expected types, and the allowed shape of structured API mapping
+   objects. `filter.config` remains intentionally permissive because
+   individual filter plugins own their own config schemas.
+3. For registry mode, check that `dgp.filter.http.dubboproxy` has usable
+   `dubboProxyConfig.registries`. For direct mode, check
+   `integrationRequest.url`, `protocol`, `parameterTypes`, and
+   `serialization`.
+
+If validation fails, fix *before* trying to boot pixiu — boot-time
+errors are more cryptic than config-shape mistakes found by inspection.
+
+### Step 5 — Smoke Test
+
+1. `go run ./cmd/pixiu/... gateway start -c configs/conf.yaml -a configs/api_config.yaml`
+   (or the binary equivalent).
+2. `curl -v -X POST http://localhost:<port>/<path> ...`
+3. If you get HTTP 5xx, inspect Pixiu server logs near the request
+   timestamp and match the first error to the config area it references.
+
+## Cross-Cutting Rules
+
+### Always
+
+- Base generated yaml on the current `pkg/config/api_config.go` and
+  `pkg/filter/http/remote/dubbo_handler.go`, not on older examples. Use
+  `parameterTypes`, not legacy `paramTypes`.
+- Use supported `mapType` values exactly as pixiu's `constant.JTypeMapper`
+  defines them. `String` and `bool` are not valid current-source values;
+  use `string` / `java.lang.String` and `boolean`.
+- Keep numeric `mappingParams[].mapTo` values aligned with Java method
+  argument order. `mapTo: "0"` is the first Dubbo argument.
+- Specify `group` and `version` explicitly, even if empty. Empty-string
+  is valid; missing key is not.
+- Put `dgp.filter.http.apiconfig` **before** any proxy filter in
+  `http_filters`.
+- Timeouts: give `methods[].timeout` a value shorter than the
+  listener's `idle_timeout` in `conf.yaml`.
+
+### Never
+
+- Generate top-level `paramTypes`; current pixiu uses `parameterTypes`.
+- Write `String` or `bool` as a `mapType`. Current pixiu accepts
+  `string` / `java.lang.String` and `boolean`.
+- Mix `requestType: http` and Dubbo fields in the same
+  `integrationRequest`. Pick one.
+- Put a registry-backed Dubbo provider address in HTTP backend fields
+  such as `host` or `path`. For direct/no-registry Dubbo calls, use
+  `integrationRequest.url` and also set `parameterTypes` and
+  `serialization`.
+- Put the Dubbo registry configuration in `api_config.yaml`. It lives
+  in `conf.yaml` under `dgp.filter.http.dubboproxy` and, for dynamic API
+  discovery, under `adapters`.
+- Use the literal object from the request body as a single arg when
+  the Dubbo side expects separate primitives. Use
+  `mappingParams[].mapTo` with an index per arg.
+
+## Common Pitfalls
+
+1. **`no provider found`** at boot — registry is up but the adapter
+   cannot resolve the interface. Check `interface`, `group`, `version`
+   spelling; `group` is case-sensitive.
+2. **500 with `generic invoke failed: ClassNotFound`** — type strings
+   came from `parameterTypes`, `opt.types`, or request body `types` and
+   do not match classes visible to the provider. Prefer exact Java
+   class names and provider-required POJO class metadata in the JSON
+   body.
+3. **Response is `{}` / empty** — `mappingParams` is empty or wrong,
+   so the Dubbo method got `null` args and returned its default value.
+4. **Timeout set in the wrong layer** — `methods[].timeout` becomes the
+   Dubbo call deadline; `dubboProxyConfig.timeout_config` controls the
+   Dubbo client side. If the Dubbo call is slow, check the method
+   timeout first, then the proxy timeout config.
+5. **Header/query name case** — `inboundRequest.headers[].name` is
+   case-insensitive on match but case-sensitive in logs; normalize in
+   docs.
+6. **Nacos namespace confusion** — `namespace` vs `namespaceId` vs
+   `group` vs `groupName`. pixiu's Nacos adapter uses `namespace` and
+   `group`; if you are setting up a new adapter from scratch, read
+   the adapter source under `pkg/adapter/` for the actual field
+   names.
+
+## Source Files To Read
+
+- `pkg/config/api_config.go`
+- `pkg/filter/http/remote/dubbo_handler.go`
+- `pkg/client/dubbo/types.go`
+- `pkg/client/dubbo/typeconv.go`
+- `pkg/client/dubbo/dubbo.go`
+- Existing `configs/api_config.yaml` and sample `api_config.yaml`
+  files.
+- Pixiu server logs for the first error near a failing request.
diff --git a/skills/pixiu-llm-gateway/SKILL.md b/skills/pixiu-llm-gateway/SKILL.md
new file mode 100644
index 00000000..b1f126d8
--- /dev/null
+++ b/skills/pixiu-llm-gateway/SKILL.md
@@ -0,0 +1,293 @@
+---
+name: pixiu-llm-gateway
+description: |
+  Configure Apache dubbo-go-pixiu as an AI / LLM gateway. Use when the
+  user asks to proxy OpenAI-compatible, Claude-compatible, Gemini, vLLM,
+  DeepSeek, or self-hosted model endpoints; configure `dgp.filter.llm.proxy`,
+  `dgp.filter.llm.tokenizer`, `dgp.filter.ai.kvcache`, LLM retry/fallback,
+  endpoint `llm_meta`, token metrics, LMCache/vLLM cache-aware routing, or
+  Nacos LLM registry discovery. This skill is for Pixiu `conf.yaml`, not
+  `api_config.yaml`.
+allowed-tools: [Read, Grep, Glob, Edit, Write, Bash]
+metadata:
+  version: "0.1.1"
+  domain: ai-gateway
+  scope: generate-and-validate
+  triggers: ["LLM gateway", "AI gateway", "OpenAI proxy", "Claude proxy", "vLLM", "DeepSeek", "kvcache", "LMCache", "tokenizer", "token billing", "llm_meta", "dgp.filter.llm.proxy"]
+  pixiu_min_version: "0.6.0"
+  experimental: true
+  role: specialist
+---
+
+# pixiu-llm-gateway — Configuring Pixiu as an LLM Gateway
+
+Pixiu's LLM path is config-driven. The gateway routes normal HTTP
+requests to LLM-compatible upstreams through `dgp.filter.llm.proxy`,
+optionally records token metrics with `dgp.filter.llm.tokenizer`, and
+optionally hints cache-local routing with `dgp.filter.ai.kvcache`.
+
+This skill writes `conf.yaml` fragments. It does not write
+`api_config.yaml`; HTTP-to-Dubbo API mapping is a different code path.
+
+## When to Use
+
+Use this skill when the user wants to:
+
+- Expose an OpenAI-compatible chat/completions endpoint through Pixiu.
+- Route between multiple LLM providers or self-hosted vLLM instances.
+- Configure endpoint-level API keys, retry policy, fallback, or health
+  cooldown through `llm_meta`.
+- Enable token metrics / token accounting through
+  `dgp.filter.llm.tokenizer`.
+- Enable KV cache-aware routing through `dgp.filter.ai.kvcache`.
+- Discover LLM endpoints dynamically from Nacos via
+  `dgp.adapter.llmregistrycenter`.
+
+Do not use this skill for:
+
+- HTTP to Dubbo routes; use `pixiu-http-to-dubbo`.
+- MCP tool exposure; use `pixiu-mcp-integration`.
+- Authoring a new filter implementation; use `pixiu-filter-author`.
+
+## Step 0 — Verify Current Source First
+
+The LLM module is moving quickly. Before generating yaml, read the
+current repo shape:
+
+1. `pkg/common/constant/key.go` for the exact Kind strings.
+2. `pkg/filter/llm/proxy/filter.go` for proxy config, endpoint
+   selection, API key injection, retry, and fallback behavior.
+3. `pkg/filter/llm/tokenizer/tokenizer.go` for token metric behavior.
+4. `pkg/filter/ai/kvcache/config.go` and `handlers.go` for cache-aware
+   routing and the `endpoint.id` contract.
+5. `pkg/model/llm.go`, `pkg/model/cluster.go`, and `pkg/model/base.go`
+   for `llm_meta`, endpoint, and `socket_address` fields.
+6. `configs/ai_kvcache_config.yaml` and docs under `docs/ai/` if
+   present.
+
+If source disagrees with this skill, trust source and note the
+difference in the final answer.
+
+## Step 1 — Gather the Eight Things
+
+Do not produce a final config until these are explicit:
+
+1. HTTP listener address and route prefix/path, usually `/v1/`.
+2. Upstream mode: static `clusters[]` or Nacos LLM registry.
+3. Provider endpoints: host/domain, scheme (`http` or `https`), and
+   path convention (`/v1/chat/completions`, `/v1/completions`, etc.).
+4. API key handling: per-endpoint `llm_meta.api_key`, injected secret
+   placeholder, or caller-supplied Authorization header.
+5. Retry policy per endpoint: `NoRetry`, `CountBased`, or
+   `ExponentialBackoff`.
+6. Fallback behavior: whether endpoint failure should move to the next
+   endpoint in the same cluster.
+7. Token metrics: whether to add `dgp.filter.llm.tokenizer` and whether
+   console logging is acceptable.
+8. KV cache: whether vLLM `/tokenize` and LMCache controller endpoints
+   exist, and whether LMCache `instance_id` values match Pixiu
+   `endpoint.id`.
+
+It is fine to propose safe defaults, but make assumptions explicit.
+
+## Step 2 — Shape the Static Gateway Config
+
+Minimal static gateway shape:
+
+```yaml
+static_resources:
+  listeners:
+    - name: "net/http"
+      protocol_type: "HTTP"
+      address:
+        socket_address:
+          address: "0.0.0.0"
+          port: 8888
+      filter_chains:
+        filters:
+          - name: dgp.filter.httpconnectionmanager
+            config:
+              route_config:
+                routes:
+                  - match:
+                      prefix: "/v1/"
+                    route:
+                      cluster: "llm_cluster"
+                      cluster_not_found_response_code: 503
+              http_filters:
+                - name: dgp.filter.llm.tokenizer
+                  config:
+                    log_to_console: false
+                - name: dgp.filter.llm.proxy
+                  config:
+                    scheme: "https"
+                    timeout: "60s"
+  clusters:
+    - name: "llm_cluster"
+      lb_policy: "RoundRobin"
+      endpoints:
+        - id: "openai-primary"
+          socket_address:
+            domains:
+              - "api.openai.com"
+          llm_meta:
+            provider: "openai"
+            api_key: "${OPENAI_API_KEY}"
+            fallback: true
+            health_check_interval: 5000
+            retry_policy:
+              name: "ExponentialBackoff"
+              config:
+                times: 3
+                initialInterval: "200ms"
+                maxInterval: "5s"
+                multiplier: 2.0
+```
+
+Notes:
+
+- `dgp.filter.llm.proxy` forwards the original request path and query
+  to the selected endpoint. If the client calls `/v1/chat/completions`,
+  the upstream receives that same path.
+- `socket_address.domains[0]` should be a host, not a full URL and not
+  a path-bearing value. Current `llm.proxy` puts it into `url.URL.Host`;
+  use `api.openai.com`, not `https://api.openai.com` or
+  `api.openai.com/v1`.
+- `dgp.filter.llm.proxy.config.scheme` is filter-level. Every endpoint
+  reached by the same HTTP filter instance uses that one scheme, so do
+  not mix `https` providers and `http` local vLLM endpoints in the same
+  route/filter configuration. Split them by route/listener/cluster
+  arrangement or normalize the upstream scheme.
+- When correcting adversarial configs, explicitly say: `scheme` is
+  filter-level, `socket_address.domains` is host-only, and full URLs
+  such as `https://api.openai.com/v1` do not belong in `domains`.
+- `lb_policy` must be one of `Rand`, `RoundRobin`, `RingHashing`,
+  `MaglevHashing`, or `WeightRandom`; reject `lb` and check
+  `pkg/model/cluster.go` before inventing a policy.
+- Endpoint API keys are injected as `Authorization: Bearer <api_key>`.
+  Do not hard-code production secrets; use placeholders or the user's
+  secret management convention.
+- `dgp.filter.llm.tokenizer` should be before `dgp.filter.llm.proxy` so
+  Decode starts timing before the upstream call. Its Encode phase reads
+  proxy attempt data and token usage from the response.
+- Tokenizer metrics read OpenAI-style `usage` fields for unary responses
+  and SSE `data:` frames for streaming responses. If upstream omits token
+  usage, report partial metrics instead of inventing counts.
+
+## Step 3 — Add KV Cache Only When the Contract Exists
+
+KV cache-aware routing is optional. It only works when LMCache returns
+an `instance_id` that matches a Pixiu endpoint `id`.
+
+```yaml
+http_filters:
+  - name: dgp.filter.ai.kvcache
+    config:
+      enabled: true
+      vllm_endpoint: "http://127.0.0.1:8000"
+      lmcache_endpoint: "http://127.0.0.1:9000"
+      default_model: "Qwen2.5-3B-Instruct"
+      request_timeout: "2s"
+      lookup_routing_timeout: "50ms"
+      hot_window: "5m"
+      hot_max_records: 300
+      token_cache:
+        enabled: true
+        max_size: 1024
+        ttl: "10m"
+      cache_strategy:
+        enable_compression: true
+        enable_pinning: true
+        enable_eviction: true
+        load_threshold: 0.7
+        memory_threshold: 0.85
+        hot_content_threshold: 10
+        pin_instance_id: "vllm-instance-1"
+        pin_location: "LocalCPUBackend"
+        compress_instance_id: "vllm-instance-1"
+        compress_location: "LocalCPUBackend"
+        compress_method: "zstd"
+        evict_instance_id: "vllm-instance-1"
+  - name: dgp.filter.llm.tokenizer
+    config:
+      log_to_console: false
+  - name: dgp.filter.llm.proxy
+    config:
+      scheme: "http"
+      timeout: "60s"
+```
+
+Before generating a kvcache config, read the current
+`pkg/filter/ai/kvcache/` source and any checked-in AI config examples.
+
+## Step 4 — Use Nacos Discovery Only for Dynamic Endpoint Discovery
+
+Static clusters are simpler. Use `dgp.adapter.llmregistrycenter` only
+when LLM services register endpoint metadata into Nacos.
+
+```yaml
+adapters:
+  - id: "llm-nacos"
+    name: dgp.adapter.llmregistrycenter
+    config:
+      registries:
+        nacos:
+          protocol: nacos
+          address: "127.0.0.1:8848"
+          timeout: "5s"
+          group: "test_llm_registry_group"
+          namespace: "public"
+```
+
+The Nacos instance metadata must include `cluster`, `id`, and LLM
+metadata keys such as `llm-meta.api_key`,
+`llm-meta.retry_policy.name`, and `llm-meta.fallback`. Read the current
+LLM registry adapter source before generating Nacos instructions.
+
+## Step 5 — Validate
+
+Before booting Pixiu, inspect `conf.yaml` directly. Check yaml syntax,
+filter order, required LLM cluster fields, `llm_meta` placement,
+kvcache instance-id references, and Nacos adapter basics.
+
+## Cross-Cutting Rules
+
+### Always
+
+- Generate `conf.yaml` fragments, not `api_config.yaml`.
+- Use exact current Kind strings: `dgp.filter.llm.proxy`,
+  `dgp.filter.llm.tokenizer`, `dgp.filter.ai.kvcache`, and
+  `dgp.adapter.llmregistrycenter`.
+- Use singular `llm-meta.api_key` in Nacos metadata. Older docs may say
+  `llm-meta.api_keys`, but current source reads only the singular key.
+- Put `dgp.filter.ai.kvcache` and `dgp.filter.llm.tokenizer` before
+  `dgp.filter.llm.proxy` when enabled.
+- Keep `llm_meta` under each `clusters[].endpoints[]` entry.
+- Use endpoint `id` values that can be matched by kvcache / LMCache
+  instance IDs.
+- Use duration strings with units: `"60s"`, `"200ms"`, `"5m"`.
+
+### Never
+
+- Put LLM provider credentials in `api_config.yaml`.
+- Put `llm_meta` under the cluster instead of endpoints.
+- Put `scheme` under individual endpoints or put full URLs in
+  `socket_address.domains`. The proxy has one filter-level scheme and
+  endpoint domains must be host-only.
+- Use Nacos `llm-meta.api_keys` plural for current source.
+- Use old names such as `dgp.filter.http.llm.proxy` unless Step 0 proves
+  the target branch changed the Kind constants.
+- Enable kvcache without `vllm_endpoint`, `lmcache_endpoint`, and an
+  endpoint-id matching story.
+- Promise cost billing from config alone. Current tokenizer records
+  token metrics; pricing/currency math belongs outside the current
+  filter unless the target branch adds it.
+
+## Source Files To Read
+
+- `pkg/filter/llm/proxy/`
+- `pkg/filter/llm/tokenizer/`
+- `pkg/filter/ai/kvcache/`
+- `pkg/adapter/llmregistry/`
+- `pkg/model/llm.go`, `pkg/model/cluster.go`, and current AI config
+  examples.
diff --git a/skills/pixiu-mcp-integration/SKILL.md b/skills/pixiu-mcp-integration/SKILL.md
new file mode 100644
index 00000000..ded4b2ba
--- /dev/null
+++ b/skills/pixiu-mcp-integration/SKILL.md
@@ -0,0 +1,231 @@
+---
+name: pixiu-mcp-integration
+description: |
+  Use when configuring dubbo-go-pixiu as an MCP gateway:
+  `dgp.filter.mcp.mcpserver`, Streamable HTTP/SSE, `tools/list`,
+  `tools/call`, OAuth/JWT `dgp.filter.http.auth.mcp`, Nacos
+  `dgp.adapter.mcpserver`, or stdio MCP bridge guidance.
+allowed-tools: [Read, Grep, Glob, Edit, Write, Bash]
+metadata:
+  version: "0.1.1"
+  domain: ai-gateway
+  scope: generate-and-validate
+  triggers: ["MCP", "Model Context Protocol", "mcpserver", "tools/list", "tools/call", "Mcp-Session-Id", "dgp.filter.mcp.mcpserver", "dgp.filter.http.auth.mcp", "mcp gateway", "MCP tool"]
+  pixiu_min_version: "0.6.0"
+  experimental: true
+  role: specialist
+---
+
+# pixiu-mcp-integration — Exposing HTTP APIs as MCP Tools
+
+Pixiu's current MCP integration is an HTTP filter that speaks MCP
+Streamable HTTP/SSE to clients and maps `tools/call` to backend HTTP
+clusters. It can also expose resources, resource templates, prompts,
+and optional OAuth/JWT protection.
+
+Important boundary: current Pixiu does not directly spawn or manage
+stdio MCP servers. For a stdio server, put an external bridge in front
+of it, then configure Pixiu to call the bridge as an HTTP backend tool.
+
+## When to Use
+
+Use this skill when the user wants to:
+
+- Expose existing backend HTTP APIs as MCP tools.
+- Configure MCP endpoint metadata, tools, resources, templates, or
+  prompts.
+- Support Streamable HTTP / SSE MCP clients.
+- Protect the MCP endpoint with OAuth 2.0 protected-resource metadata
+  and JWT validation.
+- Load MCP tool definitions dynamically from Nacos.
+- Integrate stdio MCP servers by documenting the required bridge.
+
+Do not use this skill for:
+
+- LLM model proxying; use `pixiu-llm-gateway`.
+- HTTP to Dubbo API mapping; use `pixiu-http-to-dubbo`.
+- Writing a new filter implementation; use `pixiu-filter-author`.
+
+## Step 0 — Verify Current Source First
+
+Read current source before generating config:
+
+1. `pkg/common/constant/key.go` for Kind strings.
+2. `pkg/model/mcpserver.go` for `McpServerConfig`, tool args,
+   resources, templates, and prompts.
+3. `pkg/filter/mcp/mcpserver/plugin.go`, `filter.go`, `handlers.go`,
+   and `transport/` for endpoint matching, Streamable HTTP/SSE, and
+   JSON-RPC handling.
+4. `pkg/filter/auth/mcp/config.go` and `filter.go` for optional auth.
+5. `pkg/adapter/mcpserver/registrycenter.go` and
+   `pkg/adapter/mcpserver/registry/nacos/` for dynamic tool discovery.
+6. `docs/ai/mcp/mcp.md` if present.
+
+If current source disagrees with this skill, trust the source and note
+the difference.
+
+## Step 1 — Gather the Nine Things
+
+Do not generate final config until these are explicit:
+
+1. MCP endpoint path, usually `/mcp`.
+2. Server name, version, description, and instructions.
+3. Static tools or Nacos-managed tools.
+4. For every static tool: name, description, upstream cluster,
+   request method/path/timeout, and argument schema.
+5. Backend clusters for each tool.
+6. Whether resources, resource templates, or prompts should be exposed.
+7. Transport expectation: JSON POST only, SSE GET stream, or both.
+8. Auth requirement: none, or `dgp.filter.http.auth.mcp` with issuer,
+   JWKS URL, resource URI, and protected cluster.
+9. If the user says stdio MCP: which external bridge will expose it as
+   HTTP/SSE. Pixiu itself does not run the stdio process.
+
+## Step 2 — Shape the Static MCP Server Config
+
+Before generating a static tool config, read `pkg/model/mcpserver.go`
+and the current MCP filter source. Keep the generated config compact but
+include these essentials:
+
+- `endpoint` must match the client URL path. Current filter checks
+  `ctx.Request.URL.Path == cfg.Endpoint`.
+- The `mcp-backend` route cluster is a routing anchor for the MCP
+  endpoint. Each tool has its own `cluster` for the actual backend call.
+- Declare every static tool cluster under `static_resources.clusters[]`.
+- Put `dgp.filter.mcp.mcpserver` before `dgp.filter.http.httpproxy`.
+- `tools/list`, `resources/list`, `prompts/list`, `initialize`,
+  `ping`, and `notifications/initialized` are terminal methods handled
+  by the MCP filter.
+- `tools/call` continues through the chain to an HTTP proxy, then
+  Encode converts the backend response into MCP tool-call output.
+- When explaining ordering, spell it out as
+  `dgp.filter.http.auth.mcp` before `dgp.filter.mcp.mcpserver`, and
+  `dgp.filter.mcp.mcpserver` before `dgp.filter.http.httpproxy`.
+  Terminal methods (`initialize`, `tools/list`, `resources/list`,
+  `prompts/list`, `ping`) stop at the MCP filter; `tools/call`
+  continues to `httpproxy`.
+- Current source declares `request.headers`, but `buildBackendRequest`
+  does not apply them yet. Do not rely on static tool headers for
+  runtime behavior; request bodies still get `Content-Type:
+  application/json` automatically.
+
+## Step 3 — Add OAuth/JWT Protection When Required
+
+Place `dgp.filter.http.auth.mcp` before `dgp.filter.mcp.mcpserver`:
+
+```yaml
+http_filters:
+  - name: dgp.filter.http.auth.mcp
+    config:
+      resource_metadata:
+        path: "/.well-known/oauth-protected-resource/mcp"
+        resource: "https://mcp.example.com/mcp"
+        authorization_servers:
+          - "https://auth.example.com"
+      providers:
+        - name: "main"
+          issuer: "https://auth.example.com"
+          jwks: "https://auth.example.com/.well-known/jwks.json"
+          audience: "https://mcp.example.com/mcp"
+      rules:
+        - cluster: "mcp-backend"
+  - name: dgp.filter.mcp.mcpserver
+    config: ...
+```
+
+Auth rules match the route entry's cluster. If `rules[].cluster` does
+not match the MCP route cluster, requests will not be protected.
+On successful validation the current auth filter removes the
+`Authorization` header before forwarding. Do not promise that the
+caller token reaches the tool backend; if the backend needs credentials,
+design an explicit downstream auth strategy instead of relying on the
+validated bearer token being forwarded.
+Safe alternatives are backend-side service auth, an external bridge or
+proxy that injects credentials, mTLS or network policy, or a source
+change that explicitly applies outbound headers.
+
+Before generating auth config, read the current MCP auth filter source.
+
+## Step 4 — Use Nacos Only for Dynamic Tool Definitions
+
+Static tools are easier. Use `dgp.adapter.mcpserver` only when a Nacos
+MCP registry supplies tool definitions.
+
+```yaml
+adapters:
+  - id: "mcp-nacos-adapter"
+    name: dgp.adapter.mcpserver
+    config:
+      registries:
+        nacos:
+          protocol: "nacos"
+          address: "127.0.0.1:8848"
+          timeout: "5s"
+          username: "nacos"
+          password: "nacos"
+          namespace: ""
+          group: "DEFAULT_GROUP"
+```
+
+The listener still needs `dgp.filter.mcp.mcpserver`; the adapter updates
+the in-process tool registry and may register endpoints for tools with
+`backend_url`.
+
+Before generating Nacos instructions, read the current MCP registry
+adapter source.
+
+## Step 5 — Validate and Smoke Test
+
+Before booting Pixiu, inspect `conf.yaml` directly. Check yaml syntax,
+filter order, MCP server config shape, route targets, static tool
+definitions, auth settings, and Nacos adapter basics.
+
+Smoke-test Streamable HTTP:
+
+```sh
+curl -i -X POST http://localhost:8888/mcp \
+  -H 'Content-Type: application/json' \
+  -H 'Accept: application/json, text/event-stream' \
+  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","clientInfo":{"name":"curl","version":"1.0.0"}}}'
+```
+
+For SSE, the client should call `GET /mcp` with
+`Accept: text/event-stream` and then reuse the returned
+`Mcp-Session-Id` on POST requests.
+
+## Cross-Cutting Rules
+
+### Always
+
+- Use exact current Kinds: `dgp.filter.mcp.mcpserver`,
+  `dgp.filter.http.auth.mcp`, and `dgp.adapter.mcpserver`.
+- Put auth before MCP server when auth is enabled.
+- Keep `endpoint` and route prefix/path aligned.
+- Ensure every tool `cluster` exists in `static_resources.clusters[]`
+  unless it is dynamically supplied by the adapter.
+- Use arg `in` values `path`, `query`, or `body`.
+- Use arg `type` values `string`, `integer`, `number`, or `boolean`.
+
+### Never
+
+- Claim Pixiu can directly run a stdio MCP server. It needs an external
+  HTTP/SSE bridge for stdio-based servers.
+- Put `tools` at top level; they belong under the MCP filter config.
+- Put `dgp.filter.mcp.mcpserver` after `httpproxy`; terminal MCP
+  methods must be handled before proxying.
+- Forget `dgp.filter.http.httpproxy` when `tools/call` needs to reach a
+  backend HTTP service.
+- Configure auth `rules[].cluster` with the tool backend cluster when
+  the MCP route itself uses a different route cluster.
+- Rely on `request.headers` for static tool backend credentials in the
+  current source. The model field exists, but `buildBackendRequest` does
+  not apply it yet; use an external bridge/proxy, backend-side auth, or
+  a source change before promising static header injection.
+
+## Source Files To Read
+
+- `pkg/model/mcpserver.go`
+- `pkg/filter/mcp/mcpserver/`
+- `pkg/filter/auth/mcp/`
+- `pkg/adapter/mcpserver/`
+- `docs/ai/mcp/` if present.