[
https://issues.apache.org/jira/browse/GROOVY-12633?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul King updated GROOVY-12633:
-------------------------------
Description:
h2. Description
Follow-ups from a post-merge review of {{groovy-mcp}} (GROOVY-12487). The
module works as described for ordinary use. The items below are a claim the
server makes about itself that doesn't hold, two interoperability problems,
behaviour problems found by driving the server over stdin, missing tests, and
documentation of how the module is kept in step with the rest of Groovy.
h2. 1. A {{--classpath}} jar's global AST transformations run on every query
A jar on {{--classpath}} that registers a global AST transformation
({{META-INF/services/org.codehaus.groovy.transform.ASTTransformation}}) runs on
every compile the server does. A transformation whose static initializer wrote
a marker file ran on a single {{groovy_diagnostics}} call on {{text}}. Local
transformations triggered by annotations in the queried source, and macro
methods, run the same way.
Only {{@Grab}} and {{@ASTTest}} are disabled. Yet:
* {{groovy://server/limits}} hard-codes {{"execution": false}}.
* ARCHITECTURE.md says "Compiling is not executing".
* The user guide says the server "does not run the source it is shown", which
is true for the source but not for the classpath.
A model cannot widen the classpath, so this is the operator's trust decision.
The docs, and the limits resource, should say plainly that code on
{{--classpath}} can run at compile time. Either rename the {{execution}} field
or describe exactly what it covers.
h2. 2. A {{--root}} reached through a symlink rejects the server's own URIs
{{PathPolicy}} checks the lexical path against the root as given, while results
carry real-path URIs. With {{--root /tmp/x}} on macOS, where {{/tmp}} is a link
to {{/private/tmp}}:
* {{groovy_symbols}} returns {{file:///private/tmp/x/Sample.groovy}}.
* Passing that URI, or the same absolute path, back to a tool gives "path is
outside the workspace root".
It fails closed, but breaks the normal round trip under macOS {{/tmp}} and
{{$TMPDIR}}, and Windows junctions. Accept a path whose real path is inside
{{root.toRealPath()}}.
h2. 3. Tool {{inputSchema}} uses a top-level {{oneOf}}
The 12 source tools publish an {{inputSchema}} whose top level is a {{oneOf}}
over {{text}} / {{path}} / {{uri}}. Some LLM tool APIs restrict {{oneOf}},
{{anyOf}} and {{allOf}} at the top level of an input schema. A client that
passes the schema through unchanged could then fail to offer or call these
tools.
Checked so far:
* *Claude Code:* works. Headless {{claude -p}} with only this server loaded
listed the tools and called {{groovy_diagnostics}} on {{def x = }}, which
returned "Unexpected end of input" at line 0, character 8.
* *Anthropic Messages API and OpenAI function calling (normal and {{strict}}),
with the published schema sent directly:* not yet checked.
If a direct API call rejects the schema, publish plain {{properties}} and
enforce the combination rules at run time, as is already done. If both accept
it, close this item.
h2. 4. Deeply nested input reports no diagnostics
A buffer with about 2000 nested parentheses followed by a real syntax error
returns {{"diagnostics": []}} with {{isError: false}}. {{groovy_tokens}}
returns {{[]}}. With 200 parentheses, the same buffer correctly reports
{{Unexpected '}'}}. The compiler on its own reports a parse failure, so the
error is being swallowed on the groovy-lsp side, and a model is told that
broken code is clean. A failed compile should produce a diagnostic, or an
{{isError}} result.
h3. Related
Hover through {{groovy_describe}} returns the source text up to the caret
instead of the constructor when the caret is on a constructor call ({{new
Date()}}: {{n}}, {{ne}}, {{new}}, {{D}}, {{Da}}, ...). The cause and the fix
are in groovy-lsp's {{HoverService}}; see GROOVY-XXXXX. It is listed here
because MCP clients get that text back as if it were an answer.
h2. 5. Open buffers never expire and are compiled together
Every {{text}} call with a new {{groovy-buffer:}} URI, and every {{path}},
stays open until {{groovy_close}}, and each query recompiles all open buffers
together.
* {{class Foo}} in {{groovy-buffer:a}}, then {{class Foo}} in the default
snippet, reports "Invalid duplicate class definition" naming both buffers.
* There is no cap on the number of buffers. With N buffers of up to 512 Ki
characters each, heap use and per-call cost grow with N.
Options: cap the number of open buffers, replace rather than accumulate the
default snippet, or compile a {{text}} query on its own. At a minimum, the user
guide should say that buffers persist and are compiled together.
h2. 6. Protocol details
* A large integer id is truncated: {{18446744073709551617}} is answered with
{{"id":1}} ({{JsonValues.asInt}} calls {{longValue()}}). It should be
{{-32600}} with a null id, as for other invalid ids.
* Trailing content after a JSON value is accepted:
{{\{"jsonrpc":"2.0","id":"s","method":"ping"\} trailing}} returns a result. It
should be {{-32700}}.
* A *request* (with an id) whose method starts with {{notifications/}} gets
{{result: \{\}}}. It should be {{-32601}}.
* A response object sent by the client ({{\{"id":10,"result":\{\}\}}}) gets an
error reply. JSON-RPC says not to reply to responses.
* "Not initialized" uses {{-32002}}, which MCP uses for "resource not found".
Choose an unassigned code.
* Prompt descriptors carry {{annotations}}, which the Prompt type may not
define. Confirm this against the spec, or drop it.
h2. 7. File reads
{{PathPolicy.resolveFile}} checks the size and the real path, and then
{{engine.open}} reads the path again without a size limit. A local process that
can write inside the root could replace the file with a symlink, or grow it, in
between. Read once, after the check, from the resolved real path, and limit the
read itself.
The result-size cap covers one copy of the payload, but each tool result
carries it twice ({{content}} text and {{structuredContent}}).
h2. 8. API surface
The module has no published baseline yet, so these can still change cheaply:
* Public constants {{PROTOCOL_VERSION}}, {{SUPPORTED_PROTOCOLS}} and
{{MODERN_PROTOCOL}} will change with each MCP revision. Make them non-public,
or document them as not stable.
* {{McpServerConfig}} is a record, so adding a cap later changes its canonical
constructor. Consider a builder.
* {{GroovyLanguageEngine.context()}} was made public only so the limits
resource can read the Grape and ASTTest flags and the classpath size. A narrow
accessor would avoid exposing {{LanguageServerContext}}.
* Neither {{groovy-mcp}} nor {{groovy-lsp}} carries {{@Incubating}} or a prose
incubating marker, although both track external protocols that change several
times a year.
h2. 9. Tests
* The {{@ASTTest}} test checks only that a string is absent from the output.
Use an observable side effect, such as a system property or a marker file.
* Add a test that {{@Grab}} does not resolve.
* Add a test that pins what {{--classpath}} transformations do, to match the
documentation from item 1.
* Add tests for items 2, 4, 5 and 6.
* In groovy-lsp, the new compile-epoch logic in {{LanguageServerContext}},
where a started debounced compile yields to an explicit {{recompile()}}, has no
test.
h2. 10. Documentation: keeping the module in step
Everything {{groovy-mcp}} uses is in {{groovy-lsp}} {{internal}} packages. An
API change there breaks the build, which is fine. These changes break nothing
and go unnoticed:
* *New engine features.* A capability added to {{GroovyLanguageEngine}} reaches
MCP clients only if someone adds a tool, with a hand-written schema.
* *New compile-time execution paths.* The safety claims rest on groovy-lsp
disabling {{GrabAnnotationTransformation}} and the {{@ASTTest}} thread flag. A
future core switch of the same kind would need adding there.
* *MCP spec revisions.* The supported versions are hard-coded, and revisions
arrive several times a year.
Add a "Keeping in step" section to {{subprojects/groovy-mcp/ARCHITECTURE.md}}
listing these triggers. Point at it from {{subprojects/groovy-lsp/AGENTS.md}}.
Add a client-registration example (command and arguments) to the user guide,
which currently has none.
h2. 11. Prompt arguments and slash commands
In Claude Code, an MCP prompt becomes a slash command, and the client splits
the command text at whitespace, assigning the words to the prompt's arguments
in order. For {{explain-groovy}} ({{code}} required, {{question}} optional):
* {{/mcp__groovy__explain-groovy println new Date()}} renders with {{code}} =
{{println}} and {{question}} = {{new}}.
* Quoting does not help: {{"println new Date()"}} gives {{code}} =
{{"println}}, {{question}} = {{new}}, and {{Date()"}} is dropped.
The server's prompt definition and rendering are correct; this is client
behaviour. Source code nearly always contains spaces, though, so
{{explain-groovy}} is unusable from that client as it stands.
Options:
* Make {{explain-groovy}} take only {{code}}, if a single-argument prompt
receives the whole line. {{review-groovy}} already has a single argument and
would show whether it does. Not yet tested.
* Otherwise, say in the user guide that prompts taking code are meant for
clients that collect arguments separately.
was:
h2. Description
Follow-ups from a post-merge review of {{groovy-mcp}} (GROOVY-12487). The
module works as described for ordinary use. The items below are a claim the
server makes about itself that doesn't hold, two interoperability problems,
behaviour problems found by driving the server over stdin, missing tests, and
documentation of how the module is kept in step with the rest of Groovy.
h2. 1. A {{--classpath}} jar's global AST transformations run on every query
A jar on {{--classpath}} that registers a global AST transformation
({{META-INF/services/org.codehaus.groovy.transform.ASTTransformation}}) runs on
every compile the server does. A transformation whose static initializer wrote
a marker file ran on a single {{groovy_diagnostics}} call on {{text}}. Local
transformations triggered by annotations in the queried source, and macro
methods, run the same way.
Only {{@Grab}} and {{@ASTTest}} are disabled. Yet:
* {{groovy://server/limits}} hard-codes {{"execution": false}}.
* ARCHITECTURE.md says "Compiling is not executing".
* The user guide says the server "does not run the source it is shown", which
is true for the source but not for the classpath.
A model cannot widen the classpath, so this is the operator's trust decision.
The docs, and the limits resource, should say plainly that code on
{{--classpath}} can run at compile time. Either rename the {{execution}} field
or describe exactly what it covers.
h2. 2. A {{--root}} reached through a symlink rejects the server's own URIs
{{PathPolicy}} checks the lexical path against the root as given, while results
carry real-path URIs. With {{--root /tmp/x}} on macOS, where {{/tmp}} is a link
to {{/private/tmp}}:
* {{groovy_symbols}} returns {{file:///private/tmp/x/Sample.groovy}}.
* Passing that URI, or the same absolute path, back to a tool gives "path is
outside the workspace root".
It fails closed, but breaks the normal round trip under macOS {{/tmp}} and
{{$TMPDIR}}, and Windows junctions. Accept a path whose real path is inside
{{root.toRealPath()}}.
h2. 3. Tool {{inputSchema}} uses a top-level {{oneOf}}
The 12 source tools publish an {{inputSchema}} whose top level is a {{oneOf}}
over {{text}} / {{path}} / {{uri}}. Some LLM tool APIs restrict {{oneOf}},
{{anyOf}} and {{allOf}} at the top level of an input schema. A client that
passes the schema through unchanged could then fail to offer or call these
tools.
Checked so far:
* *Claude Code:* works. Headless {{claude -p}} with only this server loaded
listed the tools and called {{groovy_diagnostics}} on {{def x = }}, which
returned "Unexpected end of input" at line 0, character 8.
* *Anthropic Messages API and OpenAI function calling (normal and {{strict}}),
with the published schema sent directly:* not yet checked.
If a direct API call rejects the schema, publish plain {{properties}} and
enforce the combination rules at run time, as is already done. If both accept
it, close this item.
h2. 4. Deeply nested input reports no diagnostics
A buffer with about 2000 nested parentheses followed by a real syntax error
returns {{"diagnostics": []}} with {{isError: false}}. {{groovy_tokens}}
returns {{[]}}. With 200 parentheses, the same buffer correctly reports
{{Unexpected '}'}}. The compiler on its own reports a parse failure, so the
error is being swallowed on the groovy-lsp side, and a model is told that
broken code is clean. A failed compile should produce a diagnostic, or an
{{isError}} result.
h3. Related
Hover through {{groovy_describe}} returns the source text up to the caret
instead of the constructor when the caret is on a constructor call ({{new
Date()}}: {{n}}, {{ne}}, {{new}}, {{D}}, {{Da}}, ...). The cause and the fix
are in groovy-lsp's {{HoverService}}; see GROOVY-XXXXX. It is listed here
because MCP clients get that text back as if it were an answer.
h2. 5. Open buffers never expire and are compiled together
Every {{text}} call with a new {{groovy-buffer:}} URI, and every {{path}},
stays open until {{groovy_close}}, and each query recompiles all open buffers
together.
* {{class Foo}} in {{groovy-buffer:a}}, then {{class Foo}} in the default
snippet, reports "Invalid duplicate class definition" naming both buffers.
* There is no cap on the number of buffers. With N buffers of up to 512 Ki
characters each, heap use and per-call cost grow with N.
Options: cap the number of open buffers, replace rather than accumulate the
default snippet, or compile a {{text}} query on its own. At a minimum, the user
guide should say that buffers persist and are compiled together.
h2. 6. Protocol details
* A large integer id is truncated: {{18446744073709551617}} is answered with
{{"id":1}} ({{JsonValues.asInt}} calls {{longValue()}}). It should be
{{-32600}} with a null id, as for other invalid ids.
* Trailing content after a JSON value is accepted:
{{\{"jsonrpc":"2.0","id":"s","method":"ping"\} trailing}} returns a result. It
should be {{-32700}}.
* A *request* (with an id) whose method starts with {{notifications/}} gets
{{result: \{\}}}. It should be {{-32601}}.
* A response object sent by the client ({{\{"id":10,"result":\{\}\}}}) gets an
error reply. JSON-RPC says not to reply to responses.
* "Not initialized" uses {{-32002}}, which MCP uses for "resource not found".
Choose an unassigned code.
* Prompt descriptors carry {{annotations}}, which the Prompt type may not
define. Confirm this against the spec, or drop it.
h2. 7. File reads
{{PathPolicy.resolveFile}} checks the size and the real path, and then
{{engine.open}} reads the path again without a size limit. A local process that
can write inside the root could replace the file with a symlink, or grow it, in
between. Read once, after the check, from the resolved real path, and limit the
read itself.
The result-size cap covers one copy of the payload, but each tool result
carries it twice ({{content}} text and {{structuredContent}}).
h2. 8. API surface
The module has no published baseline yet, so these can still change cheaply:
* Public constants {{PROTOCOL_VERSION}}, {{SUPPORTED_PROTOCOLS}} and
{{MODERN_PROTOCOL}} will change with each MCP revision. Make them non-public,
or document them as not stable.
* {{McpServerConfig}} is a record, so adding a cap later changes its canonical
constructor. Consider a builder.
* {{GroovyLanguageEngine.context()}} was made public only so the limits
resource can read the Grape and ASTTest flags and the classpath size. A narrow
accessor would avoid exposing {{LanguageServerContext}}.
* Neither {{groovy-mcp}} nor {{groovy-lsp}} carries {{@Incubating}} or a prose
incubating marker, although both track external protocols that change several
times a year.
h2. 9. Tests
* The {{@ASTTest}} test checks only that a string is absent from the output.
Use an observable side effect, such as a system property or a marker file.
* Add a test that {{@Grab}} does not resolve.
* Add a test that pins what {{--classpath}} transformations do, to match the
documentation from item 1.
* Add tests for items 2, 4, 5 and 6.
* In groovy-lsp, the new compile-epoch logic in {{LanguageServerContext}},
where a started debounced compile yields to an explicit {{recompile()}}, has no
test.
h2. 10. Documentation: keeping the module in step
Everything {{groovy-mcp}} uses is in {{groovy-lsp}} {{internal}} packages. An
API change there breaks the build, which is fine. These changes break nothing
and go unnoticed:
* *New engine features.* A capability added to {{GroovyLanguageEngine}} reaches
MCP clients only if someone adds a tool, with a hand-written schema.
* *New compile-time execution paths.* The safety claims rest on groovy-lsp
disabling {{GrabAnnotationTransformation}} and the {{@ASTTest}} thread flag. A
future core switch of the same kind would need adding there.
* *MCP spec revisions.* The supported versions are hard-coded, and revisions
arrive several times a year.
Add a "Keeping in step" section to {{subprojects/groovy-mcp/ARCHITECTURE.md}}
listing these triggers. Point at it from {{subprojects/groovy-lsp/AGENTS.md}}.
Add a client-registration example (command and arguments) to the user guide,
which currently has none.
> Follow-ups from a post-merge review of groovy-mcp (GROOVY-12487)
> ----------------------------------------------------------------
>
> Key: GROOVY-12633
> URL: https://issues.apache.org/jira/browse/GROOVY-12633
> Project: Groovy
> Issue Type: Bug
> Reporter: Paul King
> Priority: Major
>
> h2. Description
> Follow-ups from a post-merge review of {{groovy-mcp}} (GROOVY-12487). The
> module works as described for ordinary use. The items below are a claim the
> server makes about itself that doesn't hold, two interoperability problems,
> behaviour problems found by driving the server over stdin, missing tests, and
> documentation of how the module is kept in step with the rest of Groovy.
> h2. 1. A {{--classpath}} jar's global AST transformations run on every query
> A jar on {{--classpath}} that registers a global AST transformation
> ({{META-INF/services/org.codehaus.groovy.transform.ASTTransformation}}) runs
> on every compile the server does. A transformation whose static initializer
> wrote a marker file ran on a single {{groovy_diagnostics}} call on {{text}}.
> Local transformations triggered by annotations in the queried source, and
> macro methods, run the same way.
> Only {{@Grab}} and {{@ASTTest}} are disabled. Yet:
> * {{groovy://server/limits}} hard-codes {{"execution": false}}.
> * ARCHITECTURE.md says "Compiling is not executing".
> * The user guide says the server "does not run the source it is shown", which
> is true for the source but not for the classpath.
> A model cannot widen the classpath, so this is the operator's trust decision.
> The docs, and the limits resource, should say plainly that code on
> {{--classpath}} can run at compile time. Either rename the {{execution}}
> field or describe exactly what it covers.
> h2. 2. A {{--root}} reached through a symlink rejects the server's own URIs
> {{PathPolicy}} checks the lexical path against the root as given, while
> results carry real-path URIs. With {{--root /tmp/x}} on macOS, where {{/tmp}}
> is a link to {{/private/tmp}}:
> * {{groovy_symbols}} returns {{file:///private/tmp/x/Sample.groovy}}.
> * Passing that URI, or the same absolute path, back to a tool gives "path is
> outside the workspace root".
> It fails closed, but breaks the normal round trip under macOS {{/tmp}} and
> {{$TMPDIR}}, and Windows junctions. Accept a path whose real path is inside
> {{root.toRealPath()}}.
> h2. 3. Tool {{inputSchema}} uses a top-level {{oneOf}}
> The 12 source tools publish an {{inputSchema}} whose top level is a {{oneOf}}
> over {{text}} / {{path}} / {{uri}}. Some LLM tool APIs restrict {{oneOf}},
> {{anyOf}} and {{allOf}} at the top level of an input schema. A client that
> passes the schema through unchanged could then fail to offer or call these
> tools.
> Checked so far:
> * *Claude Code:* works. Headless {{claude -p}} with only this server loaded
> listed the tools and called {{groovy_diagnostics}} on {{def x = }}, which
> returned "Unexpected end of input" at line 0, character 8.
> * *Anthropic Messages API and OpenAI function calling (normal and
> {{strict}}), with the published schema sent directly:* not yet checked.
> If a direct API call rejects the schema, publish plain {{properties}} and
> enforce the combination rules at run time, as is already done. If both accept
> it, close this item.
> h2. 4. Deeply nested input reports no diagnostics
> A buffer with about 2000 nested parentheses followed by a real syntax error
> returns {{"diagnostics": []}} with {{isError: false}}. {{groovy_tokens}}
> returns {{[]}}. With 200 parentheses, the same buffer correctly reports
> {{Unexpected '}'}}. The compiler on its own reports a parse failure, so the
> error is being swallowed on the groovy-lsp side, and a model is told that
> broken code is clean. A failed compile should produce a diagnostic, or an
> {{isError}} result.
> h3. Related
> Hover through {{groovy_describe}} returns the source text up to the caret
> instead of the constructor when the caret is on a constructor call ({{new
> Date()}}: {{n}}, {{ne}}, {{new}}, {{D}}, {{Da}}, ...). The cause and the fix
> are in groovy-lsp's {{HoverService}}; see GROOVY-XXXXX. It is listed here
> because MCP clients get that text back as if it were an answer.
> h2. 5. Open buffers never expire and are compiled together
> Every {{text}} call with a new {{groovy-buffer:}} URI, and every {{path}},
> stays open until {{groovy_close}}, and each query recompiles all open buffers
> together.
> * {{class Foo}} in {{groovy-buffer:a}}, then {{class Foo}} in the default
> snippet, reports "Invalid duplicate class definition" naming both buffers.
> * There is no cap on the number of buffers. With N buffers of up to 512 Ki
> characters each, heap use and per-call cost grow with N.
> Options: cap the number of open buffers, replace rather than accumulate the
> default snippet, or compile a {{text}} query on its own. At a minimum, the
> user guide should say that buffers persist and are compiled together.
> h2. 6. Protocol details
> * A large integer id is truncated: {{18446744073709551617}} is answered with
> {{"id":1}} ({{JsonValues.asInt}} calls {{longValue()}}). It should be
> {{-32600}} with a null id, as for other invalid ids.
> * Trailing content after a JSON value is accepted:
> {{\{"jsonrpc":"2.0","id":"s","method":"ping"\} trailing}} returns a result.
> It should be {{-32700}}.
> * A *request* (with an id) whose method starts with {{notifications/}} gets
> {{result: \{\}}}. It should be {{-32601}}.
> * A response object sent by the client ({{\{"id":10,"result":\{\}\}}}) gets
> an error reply. JSON-RPC says not to reply to responses.
> * "Not initialized" uses {{-32002}}, which MCP uses for "resource not found".
> Choose an unassigned code.
> * Prompt descriptors carry {{annotations}}, which the Prompt type may not
> define. Confirm this against the spec, or drop it.
> h2. 7. File reads
> {{PathPolicy.resolveFile}} checks the size and the real path, and then
> {{engine.open}} reads the path again without a size limit. A local process
> that can write inside the root could replace the file with a symlink, or grow
> it, in between. Read once, after the check, from the resolved real path, and
> limit the read itself.
> The result-size cap covers one copy of the payload, but each tool result
> carries it twice ({{content}} text and {{structuredContent}}).
> h2. 8. API surface
> The module has no published baseline yet, so these can still change cheaply:
> * Public constants {{PROTOCOL_VERSION}}, {{SUPPORTED_PROTOCOLS}} and
> {{MODERN_PROTOCOL}} will change with each MCP revision. Make them non-public,
> or document them as not stable.
> * {{McpServerConfig}} is a record, so adding a cap later changes its
> canonical constructor. Consider a builder.
> * {{GroovyLanguageEngine.context()}} was made public only so the limits
> resource can read the Grape and ASTTest flags and the classpath size. A
> narrow accessor would avoid exposing {{LanguageServerContext}}.
> * Neither {{groovy-mcp}} nor {{groovy-lsp}} carries {{@Incubating}} or a
> prose incubating marker, although both track external protocols that change
> several times a year.
> h2. 9. Tests
> * The {{@ASTTest}} test checks only that a string is absent from the output.
> Use an observable side effect, such as a system property or a marker file.
> * Add a test that {{@Grab}} does not resolve.
> * Add a test that pins what {{--classpath}} transformations do, to match the
> documentation from item 1.
> * Add tests for items 2, 4, 5 and 6.
> * In groovy-lsp, the new compile-epoch logic in {{LanguageServerContext}},
> where a started debounced compile yields to an explicit {{recompile()}}, has
> no test.
> h2. 10. Documentation: keeping the module in step
> Everything {{groovy-mcp}} uses is in {{groovy-lsp}} {{internal}} packages. An
> API change there breaks the build, which is fine. These changes break nothing
> and go unnoticed:
> * *New engine features.* A capability added to {{GroovyLanguageEngine}}
> reaches MCP clients only if someone adds a tool, with a hand-written schema.
> * *New compile-time execution paths.* The safety claims rest on groovy-lsp
> disabling {{GrabAnnotationTransformation}} and the {{@ASTTest}} thread flag.
> A future core switch of the same kind would need adding there.
> * *MCP spec revisions.* The supported versions are hard-coded, and revisions
> arrive several times a year.
> Add a "Keeping in step" section to {{subprojects/groovy-mcp/ARCHITECTURE.md}}
> listing these triggers. Point at it from
> {{subprojects/groovy-lsp/AGENTS.md}}. Add a client-registration example
> (command and arguments) to the user guide, which currently has none.
> h2. 11. Prompt arguments and slash commands
> In Claude Code, an MCP prompt becomes a slash command, and the client splits
> the command text at whitespace, assigning the words to the prompt's arguments
> in order. For {{explain-groovy}} ({{code}} required, {{question}} optional):
> * {{/mcp__groovy__explain-groovy println new Date()}} renders with {{code}} =
> {{println}} and {{question}} = {{new}}.
> * Quoting does not help: {{"println new Date()"}} gives {{code}} =
> {{"println}}, {{question}} = {{new}}, and {{Date()"}} is dropped.
> The server's prompt definition and rendering are correct; this is client
> behaviour. Source code nearly always contains spaces, though, so
> {{explain-groovy}} is unusable from that client as it stands.
> Options:
> * Make {{explain-groovy}} take only {{code}}, if a single-argument prompt
> receives the whole line. {{review-groovy}} already has a single argument and
> would show whether it does. Not yet tested.
> * Otherwise, say in the user guide that prompts taking code are meant for
> clients that collect arguments separately.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)