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.