Use this page when you want to build a reusable Redop plugin and need a clear mental model for context,Documentation Index
Fetch the complete documentation index at: https://redop.useagents.site/docs/llms.txt
Use this file to discover all available pages before exploring further.
.use(...), and data flow.
What a plugin is
A Redop plugin is a packagedRedop instance.
You create it with definePlugin(...), return a Redop instance from setup(...), and attach it to a server with .use(...).
At runtime, a plugin is not a separate nested app. Redop merges the plugin’s registrations into the host server.
That means a plugin can contribute:
- middleware
- lifecycle hooks
- tools
- resources
- prompts
How .use(...) works
When you call .use(pluginInstance), Redop merges the plugin into the main server.
In practice, that means:
- plugin middleware runs as part of the host server’s execution flow
- plugin hooks observe the same requests as the host server
- plugin tools, resources, and prompts become part of the host server’s MCP surface
- plugin middleware can write request-scoped data to
ctx, and later handlers can read that data
- a request enters the host server
- plugin middleware or hooks run
- the plugin writes values to
ctx - host handlers or plugin handlers read those values later in the same request
When to use a plugin
Use a plugin when:- the behavior should be reused across multiple servers or projects
- you want one installable unit for middleware, hooks, and registrations
- you want a stable extension point for a team or package
- the behavior only wraps execution
- you do not need packaging or reuse
.use(...) when:
- the behavior belongs to one server only
- you are organizing your own app into folders
- you do not need to publish or share the behavior
Build a minimal plugin
Start withdefinePlugin(...).
Pass request data through context
If a plugin needs to pass request-scoped data to handlers, write that data toctx inside middleware or hooks.
This is the most important plugin pattern in Redop.
What data belongs in ctx
Use ctx for data that should live for one request only.
Good examples:
- authenticated user or tenant information
- request IDs and correlation IDs
- rate-limit or authorization decisions
- timing data
- values derived from headers or transport metadata
ctx. Use normal module variables, a database client, or another service object for shared application state.
Build a plugin that ships tools too
A plugin can do more than middleware. It can also extend the MCP surface.Plugin options
Use plugin options when consumers need to configure behavior.Built-in plugins already use this pattern
The built-in auth helpers follow the same model. They validate request data in middleware and store the result onctx so later handlers can use it.
For example:
apiKey(...)stores the validated key onctxbearer(...)stores the parsed token onctxjwt(...)andoauth(...)store verified auth payloads onctx
ctx, handler reads from ctx”, you understand the core plugin data flow in Redop.
Typed plugin context
If a plugin declares its context shape,.use(...) carries that shape into the host app.
That means this pattern is now valid:
derive(...) functions are also merged by .use(...).
The remaining limitation is dynamic keys. If your plugin lets consumers choose a runtime key name such as options.contextKey, TypeScript cannot infer that property statically, so a cast is still normal in that one case.
Practical plugin design rules
- Keep plugin ownership narrow and obvious.
- Use middleware for request flow control and context decoration.
- Use hooks for observation and post-processing.
- Use
onAfterResponse(...)for analytics, metrics, or logging that should happen after the response is written. - Ship tools, resources, or prompts from a plugin only when the plugin truly owns that MCP surface.
- Prefer explicit names such as
notes.listorbilling.invoice.createfor tools that a plugin registers.
Common mistakes
- Building a plugin when plain middleware would be enough.
- Hiding unrelated behavior inside one plugin.
- Treating a plugin like a separate nested server instead of a merged extension.
- Using a dynamic runtime key and expecting TypeScript to know that property name automatically.