@evref-bl/pharo-mcp 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/package.json +1 -1
- package/templates/pharo-references/building-on-mcp.md +473 -0
- package/templates/pharo-references/safety-and-ecosystem.md +2 -1
- package/templates/pharo-references/tool-reference.md +6 -6
- package/templates/pharo-references/troubleshooting.md +1 -1
- package/templates/pharo-references/using-pharo-mcp.md +6 -92
- package/templates/pharo.AGENTS.md +2 -0
package/README.md
CHANGED
|
@@ -32,6 +32,22 @@ Metacello new
|
|
|
32
32
|
load.
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
The baseline groups are:
|
|
36
|
+
|
|
37
|
+
- `Core`: the server, its transport and schema machinery, and the catalog
|
|
38
|
+
tools it dispatches through
|
|
39
|
+
- `IDE`: the image tools — classes, methods, packages, tests, critiques,
|
|
40
|
+
repositories, change history, debugging
|
|
41
|
+
- `UI`: the world-menu server list
|
|
42
|
+
- `Telemetry`: the OpenTelemetry backend
|
|
43
|
+
- `default`: `Core`, `IDE`, `UI` and their tests
|
|
44
|
+
|
|
45
|
+
A project embedding the server and serving tools of its own loads `Core` alone,
|
|
46
|
+
which leaves out the image tools and everything behind them. Loading its package
|
|
47
|
+
is all its tools need: a tool that names itself is in the catalog.
|
|
48
|
+
[Building on MCP](templates/pharo-references/building-on-mcp.md) walks through
|
|
49
|
+
a tool, a resource and a prompt of your own.
|
|
50
|
+
|
|
35
51
|
Start the Streamable HTTP server:
|
|
36
52
|
|
|
37
53
|
```smalltalk
|
|
@@ -51,6 +67,7 @@ and first calls.
|
|
|
51
67
|
- [Documentation index](docs/README.md)
|
|
52
68
|
- [Getting started](templates/pharo-references/getting-started.md)
|
|
53
69
|
- [Using MCP from an agent](templates/pharo-references/using-pharo-mcp.md)
|
|
70
|
+
- [Building on MCP](templates/pharo-references/building-on-mcp.md)
|
|
54
71
|
- [Tool reference](templates/pharo-references/tool-reference.md)
|
|
55
72
|
- [Safety and ecosystem integration](templates/pharo-references/safety-and-ecosystem.md)
|
|
56
73
|
- [Troubleshooting](templates/pharo-references/troubleshooting.md)
|
package/package.json
CHANGED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# Building On MCP
|
|
2
|
+
|
|
3
|
+
MCP is a server you can embed. Loading `Core` gives you the Streamable HTTP
|
|
4
|
+
transport, the JSON-RPC and schema machinery and the catalog tools, and
|
|
5
|
+
nothing that knows about Pharo code. Everything the server then offers a
|
|
6
|
+
client — tools, resources, prompts — you write in a package of your own.
|
|
7
|
+
|
|
8
|
+
A project doing this changes nothing in MCP. Its tools live in a package of
|
|
9
|
+
its own, which it loads when it wants them served and leaves out when it does
|
|
10
|
+
not.
|
|
11
|
+
|
|
12
|
+
## Load the server alone
|
|
13
|
+
|
|
14
|
+
Depend on the baseline and ask for `Core`:
|
|
15
|
+
|
|
16
|
+
```smalltalk
|
|
17
|
+
BaselineOfMyProject >> baseline: spec
|
|
18
|
+
|
|
19
|
+
<baseline>
|
|
20
|
+
spec for: #common do: [
|
|
21
|
+
spec baseline: 'MCP' with: [
|
|
22
|
+
spec
|
|
23
|
+
repository: 'github://Evref-BL/MCP:vX.Y.Z/src'; "a release, not a branch"
|
|
24
|
+
loads: #( 'Core' ) ].
|
|
25
|
+
spec package: 'MyProject-MCP' with: [ spec requires: #( 'MyProject' 'MCP' ) ].
|
|
26
|
+
spec group: 'Tools' with: #( 'MyProject-MCP' ).
|
|
27
|
+
spec group: 'default' with: #( 'Core' 'Tests' ) ]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Two things in it are worth copying. The group is named `Tools` rather than
|
|
31
|
+
`MCP`, because Metacello refuses a group and a baseline that share one name.
|
|
32
|
+
And it is left out of `default`: your project is useful without a server, and
|
|
33
|
+
an image that does not serve MCP has no use for tools it cannot register.
|
|
34
|
+
|
|
35
|
+
The other groups are `IDE`, MCP's own image tools for classes, methods,
|
|
36
|
+
packages, tests, repositories, change history and debugging; `UI`,
|
|
37
|
+
the world-menu server list; `Telemetry`, the OpenTelemetry backend; and
|
|
38
|
+
`default`, which is `Core`, `IDE`, `UI` and their tests.
|
|
39
|
+
|
|
40
|
+
## The three things a server offers
|
|
41
|
+
|
|
42
|
+
| Kind | What it is | Who reaches for it | Subclass of | Names itself with |
|
|
43
|
+
| --- | --- | --- | --- | --- |
|
|
44
|
+
| Tool | an action with arguments | the model, on its own | `MCPTool` | `toolName` |
|
|
45
|
+
| Resource | a document to read | the host, for the user | `MCPResource` | `resourceUri` |
|
|
46
|
+
| Prompt | a message template | the user, deliberately | `MCPPrompt` | `promptName` |
|
|
47
|
+
|
|
48
|
+
They are independent. A server may have one of them, two, or all three.
|
|
49
|
+
|
|
50
|
+
## A tool
|
|
51
|
+
|
|
52
|
+
Give your tools a common superclass. It is where the things they share live —
|
|
53
|
+
the group they belong to, the icon they are drawn with, the helpers that talk
|
|
54
|
+
to your project:
|
|
55
|
+
|
|
56
|
+
```smalltalk
|
|
57
|
+
MCPTool << #MyProjectTool
|
|
58
|
+
package: 'MyProject-MCP'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```smalltalk
|
|
62
|
+
MyProjectTool class >> groupName
|
|
63
|
+
|
|
64
|
+
^ 'widgets'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The base is not served, and needs to say nothing to stay out: it answers no
|
|
68
|
+
`toolName`, so there is no tool there to register.
|
|
69
|
+
|
|
70
|
+
Then one class per tool:
|
|
71
|
+
|
|
72
|
+
```smalltalk
|
|
73
|
+
MyProjectTool << #MyProjectToolListWidgets
|
|
74
|
+
package: 'MyProject-MCP'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```smalltalk
|
|
78
|
+
MyProjectToolListWidgets class >> toolName
|
|
79
|
+
|
|
80
|
+
^ 'widget_list'
|
|
81
|
+
|
|
82
|
+
MyProjectToolListWidgets >> title
|
|
83
|
+
|
|
84
|
+
^ 'List Widgets'
|
|
85
|
+
|
|
86
|
+
MyProjectToolListWidgets >> description
|
|
87
|
+
|
|
88
|
+
^ 'List the widgets this image holds, optionally those whose name starts with a prefix. Use widget_get to read one.'
|
|
89
|
+
|
|
90
|
+
MyProjectToolListWidgets >> additionalKeywords
|
|
91
|
+
|
|
92
|
+
^ #( 'widget' 'inventory' )
|
|
93
|
+
|
|
94
|
+
MyProjectToolListWidgets >> annotations
|
|
95
|
+
|
|
96
|
+
^ MCPStructureToolAnnotations readOnly
|
|
97
|
+
|
|
98
|
+
MyProjectToolListWidgets >> buildInputSchema
|
|
99
|
+
|
|
100
|
+
^ MCPStructureInputSchema new
|
|
101
|
+
type: 'object';
|
|
102
|
+
properties: { (self
|
|
103
|
+
schemaPropertyNamed: 'prefix'
|
|
104
|
+
type: 'string'
|
|
105
|
+
description: 'Only answer widgets whose name starts with this. Defaults to all of them.') };
|
|
106
|
+
required: #( );
|
|
107
|
+
yourself
|
|
108
|
+
|
|
109
|
+
MyProjectToolListWidgets >> executeWithRequest: request
|
|
110
|
+
|
|
111
|
+
| prefix names |
|
|
112
|
+
prefix := request stringArgumentNamed: 'prefix' default: ''.
|
|
113
|
+
names := (MyProject widgetNames select: [ :each | each beginsWith: prefix ]) asArray sorted.
|
|
114
|
+
^ self
|
|
115
|
+
successResultText: names size asString , ' widget(s): ' , (', ' join: names) , '.'
|
|
116
|
+
data: { (#widgets -> names) } asDictionary
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`MyProject widgetNames` stands for your own code. What the rest is for:
|
|
120
|
+
|
|
121
|
+
- **`toolName`** is the key in one catalog shared with every other tool the
|
|
122
|
+
image has loaded, so pick a first word that is yours. `widget_list` is
|
|
123
|
+
safe; `class_search` is taken.
|
|
124
|
+
- **`description`** is read by a model deciding whether to call you. Say what
|
|
125
|
+
the tool answers and name the tools that come next, the way the example
|
|
126
|
+
points at `widget_get`.
|
|
127
|
+
- **`additionalKeywords`** are the words `tool_search` matches besides the
|
|
128
|
+
ones already in the name and title.
|
|
129
|
+
- **`annotations`** tell a client what the tool does to the image.
|
|
130
|
+
`MCPStructureToolAnnotations readOnly` promises it changes nothing;
|
|
131
|
+
`mayModify` says otherwise. A host may require a confirmation for the
|
|
132
|
+
second.
|
|
133
|
+
- **`buildInputSchema`** is the contract the server validates an incoming
|
|
134
|
+
call against before your code runs. `additionalProperties` defaults to
|
|
135
|
+
false, so a caller that misspells an argument is told so instead of having
|
|
136
|
+
it ignored. Every property takes a description; it is the only place the
|
|
137
|
+
caller learns what the argument means.
|
|
138
|
+
|
|
139
|
+
## Being served
|
|
140
|
+
|
|
141
|
+
A tool is served because its package was loaded, and naming itself is how a
|
|
142
|
+
class says it is a tool. Nothing else is needed: load `MyProject-MCP` and
|
|
143
|
+
every server in the image has `widget_list`.
|
|
144
|
+
|
|
145
|
+
It is the tool's **own** name that serves it, not an inherited one. A
|
|
146
|
+
subclass of a tool is a variation of that tool, so specialising one does not
|
|
147
|
+
claim the name it already holds — give the subclass its own `toolName` when
|
|
148
|
+
it is a tool of its own.
|
|
149
|
+
|
|
150
|
+
A class that must stay out of every catalog says so itself:
|
|
151
|
+
|
|
152
|
+
```smalltalk
|
|
153
|
+
MyProjectTestTool class >> shouldAutoRegister
|
|
154
|
+
|
|
155
|
+
^ false
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
That is the right thing for a test fixture, which would otherwise be served
|
|
159
|
+
by every server in the image whose tests are loaded.
|
|
160
|
+
|
|
161
|
+
## Reading arguments
|
|
162
|
+
|
|
163
|
+
`executeWithRequest:` receives an `MCPToolRequest` whose arguments have
|
|
164
|
+
already been validated against your schema. Read them with the typed
|
|
165
|
+
accessors rather than by hand, so a missing or oddly typed value fails in one
|
|
166
|
+
place:
|
|
167
|
+
|
|
168
|
+
```smalltalk
|
|
169
|
+
request stringArgumentNamed: 'name'.
|
|
170
|
+
request stringArgumentNamed: 'name' default: 'anything'.
|
|
171
|
+
request stringArgumentNamed: 'side' in: #( 'instance' 'class' ) default: 'instance'.
|
|
172
|
+
request booleanArgumentNamed: 'force' default: false.
|
|
173
|
+
request nonNegativeIntegerArgumentNamed: 'offset' default: 0.
|
|
174
|
+
request stringCollectionArgumentNamed: 'packages' default: #( ).
|
|
175
|
+
request hasArgumentNamed: 'prefix'.
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Answering
|
|
179
|
+
|
|
180
|
+
Two answers, both built by the tool:
|
|
181
|
+
|
|
182
|
+
```smalltalk
|
|
183
|
+
self successResultText: 'Three widgets.' data: { (#widgets -> names) } asDictionary.
|
|
184
|
+
self errorResultText: 'No widget named ' , name , '.' details: { (#known -> names) } asDictionary
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The text is what a model reads; the data is the same answer in a shape a
|
|
188
|
+
program can use. Give both. A text that says only "Done." costs the caller
|
|
189
|
+
another round trip to find out what happened.
|
|
190
|
+
|
|
191
|
+
You do not have to catch to report a failure. An exception that escapes
|
|
192
|
+
`executeWithRequest:` is answered as an error result naming your tool, the
|
|
193
|
+
error class, the message and the frames of your own code; the server leaves
|
|
194
|
+
its own frames out. Build an error result yourself when you know something
|
|
195
|
+
the exception does not, such as what the caller should have asked for.
|
|
196
|
+
|
|
197
|
+
Which of the two reaches the client is the server's choice, not the tool's.
|
|
198
|
+
`toolResponseFields` defaults to `#content`, where the data is rendered into
|
|
199
|
+
the text. `useContentAndStructuredContentToolResponses` sends both, and
|
|
200
|
+
`useStructuredContentOnlyToolResponses` sends only the structured half. The
|
|
201
|
+
structured half carries your text as `summary` and your dictionary as `data`.
|
|
202
|
+
|
|
203
|
+
### Answering a page
|
|
204
|
+
|
|
205
|
+
A tool that answers a list answers one page of it, so a long result never
|
|
206
|
+
arrives as one block a model has to read through. Three parts: the shared
|
|
207
|
+
`offset` argument, how big a page is, and the slicing. The listing tool
|
|
208
|
+
again, now that the list can be long:
|
|
209
|
+
|
|
210
|
+
```smalltalk
|
|
211
|
+
MyProjectToolListWidgets >> buildInputSchema
|
|
212
|
+
|
|
213
|
+
^ MCPStructureInputSchema new
|
|
214
|
+
type: 'object';
|
|
215
|
+
properties: { self offsetInputProperty };
|
|
216
|
+
required: #( );
|
|
217
|
+
yourself
|
|
218
|
+
|
|
219
|
+
MyProjectToolListWidgets >> defaultPageLimit
|
|
220
|
+
|
|
221
|
+
^ 25
|
|
222
|
+
|
|
223
|
+
MyProjectToolListWidgets >> executeWithRequest: request
|
|
224
|
+
|
|
225
|
+
| page |
|
|
226
|
+
page := MCPPaginationResult
|
|
227
|
+
fromEntries: MyProject widgetNames asArray sorted
|
|
228
|
+
limit: self defaultPageLimit
|
|
229
|
+
offset: (request nonNegativeIntegerArgumentNamed: 'offset' default: 0).
|
|
230
|
+
^ self
|
|
231
|
+
successResultText: page returnedCount asString , ' widget(s).'
|
|
232
|
+
data: (page asDictionaryWithEntriesKey: #widgets)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`offsetInputProperty` is the same `offset` argument every paging tool takes,
|
|
236
|
+
declared once so they all describe it the same way. `MCPPaginationResult`
|
|
237
|
+
cuts the page and answers the shape MCP's own tools answer: your entries
|
|
238
|
+
under the key you name, a `pagination` summary beside them, and `nextOffset`
|
|
239
|
+
while entries remain — a caller asks again with that offset until it stops
|
|
240
|
+
coming.
|
|
241
|
+
|
|
242
|
+
Answering `defaultPageLimit` is what puts `pageSize` in your contract, so
|
|
243
|
+
`tool_get` can tell a caller how large a page is before asking for one. A
|
|
244
|
+
tool that answers a whole result rather than pages leaves it alone.
|
|
245
|
+
|
|
246
|
+
## Groups and icons
|
|
247
|
+
|
|
248
|
+
`groupName` is how the catalog and the world-menu browser group related
|
|
249
|
+
tools. Pick one for your project and answer it on the shared superclass.
|
|
250
|
+
|
|
251
|
+
MCP reads an icon out of a tool name whose words it recognises — `class`,
|
|
252
|
+
`method`, `search`, `create` — which is how its own tools stay consistent
|
|
253
|
+
without anyone choosing. Your words are not in that vocabulary, so declare an
|
|
254
|
+
icon:
|
|
255
|
+
|
|
256
|
+
```smalltalk
|
|
257
|
+
MyProjectTool class >> toolIconSpec
|
|
258
|
+
|
|
259
|
+
^ MCPToolIconSpec baseIconNamed: #pharoHalfScale badgeIconNamed: #browse
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## A resource
|
|
263
|
+
|
|
264
|
+
A resource is a document the host can put in front of the model without the
|
|
265
|
+
model asking. Where a tool answers a question, a resource is material:
|
|
266
|
+
|
|
267
|
+
```smalltalk
|
|
268
|
+
MCPResource << #MyProjectWidgetResource
|
|
269
|
+
package: 'MyProject-MCP'
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```smalltalk
|
|
273
|
+
MyProjectWidgetResource class >> resourceUri
|
|
274
|
+
|
|
275
|
+
^ 'myproject://widgets'
|
|
276
|
+
|
|
277
|
+
MyProjectWidgetResource class >> resourceName
|
|
278
|
+
|
|
279
|
+
^ 'widgets'
|
|
280
|
+
|
|
281
|
+
MyProjectWidgetResource >> title
|
|
282
|
+
|
|
283
|
+
^ 'Widget Inventory'
|
|
284
|
+
|
|
285
|
+
MyProjectWidgetResource >> description
|
|
286
|
+
|
|
287
|
+
^ 'Every widget this image holds, as JSON.'
|
|
288
|
+
|
|
289
|
+
MyProjectWidgetResource >> mimeType
|
|
290
|
+
|
|
291
|
+
^ 'application/json'
|
|
292
|
+
|
|
293
|
+
MyProjectWidgetResource >> readContents
|
|
294
|
+
|
|
295
|
+
^ self textContents: (STONJSON toStringPretty: { (#widgets -> MyProject widgetNames) } asDictionary)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The URI is the key in the registry, and the same rule applies: a resource
|
|
299
|
+
that names itself is served.
|
|
300
|
+
|
|
301
|
+
### A family of resources
|
|
302
|
+
|
|
303
|
+
When the document is one of many, named by something in the URI, subclass
|
|
304
|
+
`MCPResourceTemplate`. It names itself with a `resourceUriTemplate` in RFC
|
|
305
|
+
6570 form, resolves one URI at a time through `resourceForUri:`, and may
|
|
306
|
+
suggest values for its variables:
|
|
307
|
+
|
|
308
|
+
```smalltalk
|
|
309
|
+
MyProjectWidgetTemplate class >> resourceUriTemplate
|
|
310
|
+
|
|
311
|
+
^ 'myproject://widget/{name}'
|
|
312
|
+
|
|
313
|
+
MyProjectWidgetTemplate >> resourceForUri: aUri
|
|
314
|
+
|
|
315
|
+
| name |
|
|
316
|
+
(aUri beginsWith: MyProjectOneWidgetResource resourceUri) ifFalse: [ ^ nil ].
|
|
317
|
+
name := aUri allButFirst: MyProjectOneWidgetResource resourceUri size.
|
|
318
|
+
^ name
|
|
319
|
+
ifEmpty: [ nil ]
|
|
320
|
+
ifNotEmpty: [ MyProjectOneWidgetResource named: name ]
|
|
321
|
+
|
|
322
|
+
MyProjectWidgetTemplate >> completionsFor: aVariableName value: aPrefix context: aContextDictionary
|
|
323
|
+
|
|
324
|
+
aVariableName = 'name' ifFalse: [ ^ #( ) ].
|
|
325
|
+
^ MyProject widgetNames select: [ :each | each beginsWith: aPrefix ]
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
A template lists through `resources/templates/list` and never answers
|
|
329
|
+
contents of its own.
|
|
330
|
+
|
|
331
|
+
What it resolves to is an ordinary resource built per URI rather than
|
|
332
|
+
registered. It carries the name it was built with, answers its own `uri`, and
|
|
333
|
+
keeps itself out of the catalog, where one entry standing for the whole family
|
|
334
|
+
would be wrong:
|
|
335
|
+
|
|
336
|
+
```smalltalk
|
|
337
|
+
MyProjectOneWidgetResource class >> resourceUri
|
|
338
|
+
|
|
339
|
+
^ 'myproject://widget/'
|
|
340
|
+
|
|
341
|
+
MyProjectOneWidgetResource class >> shouldAutoRegister
|
|
342
|
+
|
|
343
|
+
^ false
|
|
344
|
+
|
|
345
|
+
MyProjectOneWidgetResource class >> named: aName
|
|
346
|
+
|
|
347
|
+
^ self new
|
|
348
|
+
widgetName: aName;
|
|
349
|
+
yourself
|
|
350
|
+
|
|
351
|
+
MyProjectOneWidgetResource >> uri
|
|
352
|
+
|
|
353
|
+
^ self class resourceUri , self widgetName
|
|
354
|
+
|
|
355
|
+
MyProjectOneWidgetResource >> name
|
|
356
|
+
|
|
357
|
+
^ self widgetName
|
|
358
|
+
|
|
359
|
+
MyProjectOneWidgetResource >> readContents
|
|
360
|
+
|
|
361
|
+
^ self textContents: (MyProject describeWidgetNamed: self widgetName)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## A prompt
|
|
365
|
+
|
|
366
|
+
A prompt is user-controlled where a tool is model-controlled: a client
|
|
367
|
+
usually shows them as slash commands, so a prompt is something a person
|
|
368
|
+
reaches for deliberately.
|
|
369
|
+
|
|
370
|
+
```smalltalk
|
|
371
|
+
MyProjectReviewPrompt class >> promptName
|
|
372
|
+
|
|
373
|
+
^ 'review_widget'
|
|
374
|
+
|
|
375
|
+
MyProjectReviewPrompt >> title
|
|
376
|
+
|
|
377
|
+
^ 'Review A Widget'
|
|
378
|
+
|
|
379
|
+
MyProjectReviewPrompt >> description
|
|
380
|
+
|
|
381
|
+
^ 'Asks for a review of one widget.'
|
|
382
|
+
|
|
383
|
+
MyProjectReviewPrompt >> arguments
|
|
384
|
+
|
|
385
|
+
^ { (MCPStructurePromptArgument name: 'widget' description: 'The widget to review.' required: true) }
|
|
386
|
+
|
|
387
|
+
MyProjectReviewPrompt >> messagesFor: anArgumentDictionary
|
|
388
|
+
|
|
389
|
+
^ self userMessage: 'Review the widget named ' , (anArgumentDictionary at: 'widget')
|
|
390
|
+
, ' and say what you would change.'
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Like a template, a prompt may answer `completionsFor:value:context:` for its
|
|
394
|
+
own arguments.
|
|
395
|
+
|
|
396
|
+
## Shaping the server's surface
|
|
397
|
+
|
|
398
|
+
Two words govern it. A **registered** tool is in the catalog: callable by
|
|
399
|
+
name, and findable with `tool_search` and `tool_get`. An **advertised** tool
|
|
400
|
+
is also in `tools/list`, which is the list a client shows a model up front.
|
|
401
|
+
Advertising decides what is shown, not what may be called.
|
|
402
|
+
|
|
403
|
+
A tool from outside MCP is registered and discoverable, not advertised, so it
|
|
404
|
+
costs nothing in the client's context until something looks for it. Answer
|
|
405
|
+
`'static'` from `defaultExposure` to be in `tools/list` from the start.
|
|
406
|
+
|
|
407
|
+
Against that default, a server is configured directly:
|
|
408
|
+
|
|
409
|
+
```smalltalk
|
|
410
|
+
mcp := MCP new.
|
|
411
|
+
mcp addTool: MyProjectToolListWidgets. "or addTools: { A. B }"
|
|
412
|
+
mcp removeTool: 'image_evaluate'.
|
|
413
|
+
mcp advertiseTool: MyProjectToolListWidgets. "adds one to what is advertised"
|
|
414
|
+
mcp advertiseTools: { MyProjectToolListWidgets }. "replaces the advertised set"
|
|
415
|
+
mcp advertiseAllTools.
|
|
416
|
+
mcp advertiseToolDefaults.
|
|
417
|
+
mcp port: 4000.
|
|
418
|
+
mcp start.
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
`MCP startOnPort:` on the class side answers the server already on that port,
|
|
422
|
+
or builds one with the default catalog. A server you configured is started
|
|
423
|
+
through the instance, as above.
|
|
424
|
+
|
|
425
|
+
For a server holding only your own tools:
|
|
426
|
+
|
|
427
|
+
```smalltalk
|
|
428
|
+
mcp := MCP onlyTools: { MyProjectToolListWidgets. MyProjectToolGetWidget }.
|
|
429
|
+
mcp port: 4000.
|
|
430
|
+
mcp start.
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
`onlyTools:` replaces the catalog with exactly those, advertises all of them,
|
|
434
|
+
and discovers nothing afterwards. That includes the catalog tools: a server
|
|
435
|
+
built this way has no `tool_search`, `tool_get` or `tool_call` unless you add
|
|
436
|
+
them back. Add `MCPToolSearchTools`, `MCPToolGetTool` and `MCPToolCallTool`
|
|
437
|
+
when you want a surface a model can explore, or build up from `MCP new` with
|
|
438
|
+
`addTool:` instead.
|
|
439
|
+
|
|
440
|
+
## Testing what you serve
|
|
441
|
+
|
|
442
|
+
A server answers its JSON-RPC methods as plain messages, so a test needs no
|
|
443
|
+
socket and no port:
|
|
444
|
+
|
|
445
|
+
```smalltalk
|
|
446
|
+
MyProjectToolTest >> testListingAnswersEveryWidget
|
|
447
|
+
|
|
448
|
+
| result |
|
|
449
|
+
result := (MCP new useContentAndStructuredContentToolResponses; yourself)
|
|
450
|
+
rpcToolCall: 'widget_list' withParams: Dictionary new.
|
|
451
|
+
self assert: (((result at: #structuredContent) at: #data) at: #widgets) equals: #( 'alpha' 'beta' 'gamma' ).
|
|
452
|
+
self assert: (((result at: #content) first at: #text) beginsWith: '3 widget(s)')
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Assert on the data rather than on the whole text: under the default
|
|
456
|
+
`#content`, the text a client receives is your summary with the data rendered
|
|
457
|
+
underneath it, so an equality test on it breaks whenever either half changes.
|
|
458
|
+
|
|
459
|
+
`rpcResourceRead:`, `rpcPromptGet:arguments:`, `rpcToolsList`,
|
|
460
|
+
`rpcResourcesList` and `rpcPromptsList` are there for the same purpose.
|
|
461
|
+
|
|
462
|
+
## Things that bite
|
|
463
|
+
|
|
464
|
+
- **A name already taken.** The catalog holds one tool per name. Prefix with
|
|
465
|
+
a word that is yours.
|
|
466
|
+
- **A subclass of an existing tool is not served** until it names itself.
|
|
467
|
+
That is deliberate: it would otherwise claim its superclass's name and the
|
|
468
|
+
two would race for the key.
|
|
469
|
+
- **A fixture in a loaded test package is served** like anything else.
|
|
470
|
+
Answer `false` from `shouldAutoRegister`.
|
|
471
|
+
- **`onlyTools:` drops the catalog tools.** See above.
|
|
472
|
+
- **Nothing is saved.** Tools change the running image and never write it to
|
|
473
|
+
disk. `Smalltalk saveSession` is the caller's business, not the tool's.
|
|
@@ -275,7 +275,8 @@ makes one only when the image has none there. Starting a second server on a
|
|
|
275
275
|
taken port fails after a couple of seconds, and the error now says when the
|
|
276
276
|
port is held by another MCP in the same image.
|
|
277
277
|
|
|
278
|
-
An inspected `MCP` object has
|
|
278
|
+
An inspected `MCP` object has a dashboard tab and an observability tab. The
|
|
279
|
+
dashboard runs the server:
|
|
279
280
|
|
|
280
281
|
- server status, endpoint and port
|
|
281
282
|
- debug mode
|
|
@@ -43,10 +43,10 @@ mcp useContentAndStructuredContentToolResponses.
|
|
|
43
43
|
## Compact Paging
|
|
44
44
|
|
|
45
45
|
List and search tools are optimized for low token usage. A page is the tool's
|
|
46
|
-
own size, chosen for what one of its entries costs to read
|
|
47
|
-
|
|
48
|
-
another page exists, `data` includes `nextOffset`; call
|
|
49
|
-
with that `offset` to continue.
|
|
46
|
+
own size, chosen for what one of its entries costs to read, so a page of
|
|
47
|
+
classes is longer than a page of repositories. The number is not something to
|
|
48
|
+
know in advance: when another page exists, `data` includes `nextOffset`; call
|
|
49
|
+
the same tool again with that `offset` to continue.
|
|
50
50
|
|
|
51
51
|
A search answers what identifies each hit, not everything known about it.
|
|
52
52
|
`repository_search` says how many packages a repository holds rather than
|
|
@@ -55,7 +55,7 @@ rather than restating its documentation on every hit.
|
|
|
55
55
|
|
|
56
56
|
## Naming Several Targets
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Some tools act on several targets in one call. The argument that names the
|
|
59
59
|
target holds either one value or a list of them, and every other argument
|
|
60
60
|
applies to all of them:
|
|
61
61
|
|
|
@@ -124,7 +124,7 @@ attempted.
|
|
|
124
124
|
|
|
125
125
|
## The Advertised Surface
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
What a client is shown up front is the advertised surface: the catalog tools, the everyday class and method operations, `test_run` and `image_evaluate`. Which ones exactly is what `tools/list` answers, and the client already holds that list, so read it there rather than from here. Every other tool is found with `tool_search` and called by name like any other; `tool_call` does the same for a client that will only send a name its own list holds.
|
|
128
128
|
|
|
129
129
|
A server chooses its own surface before `start`:
|
|
130
130
|
|
|
@@ -37,7 +37,7 @@ http://127.0.0.1:4000
|
|
|
37
37
|
|
|
38
38
|
If you changed the port in Pharo, update the MCP client configuration.
|
|
39
39
|
|
|
40
|
-
MCP
|
|
40
|
+
MCP handles MCP calls over HTTP POST. Unsupported HTTP methods
|
|
41
41
|
return method-not-allowed instead of hanging.
|
|
42
42
|
|
|
43
43
|
## `tools/list` Is Empty Or Stale
|
|
@@ -77,96 +77,10 @@ Run `npx @evref-bl/pharo-mcp install --host <host>` in the target project to
|
|
|
77
77
|
install context-routed skills for project loading, version compatibility,
|
|
78
78
|
image-to-Git handoff, CI reproduction, and standalone MCP recovery.
|
|
79
79
|
|
|
80
|
-
##
|
|
80
|
+
## Serving Your Own
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
MCPTool << #MyProjectMCPTool
|
|
88
|
-
slots: {};
|
|
89
|
-
package: 'MyProject-MCP'
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
```smalltalk
|
|
93
|
-
MyProjectMCPTool class >> toolName
|
|
94
|
-
|
|
95
|
-
^ 'my_project_tool'
|
|
96
|
-
|
|
97
|
-
MyProjectMCPTool >> description
|
|
98
|
-
|
|
99
|
-
^ 'Do one project-specific operation.'
|
|
100
|
-
|
|
101
|
-
MyProjectMCPTool >> buildInputSchema
|
|
102
|
-
|
|
103
|
-
^ MCPStructureInputSchema new
|
|
104
|
-
type: 'object';
|
|
105
|
-
properties: #( );
|
|
106
|
-
required: #( );
|
|
107
|
-
additionalProperties: false;
|
|
108
|
-
yourself
|
|
109
|
-
|
|
110
|
-
MyProjectMCPTool >> executeWithRequest: request
|
|
111
|
-
|
|
112
|
-
^ self
|
|
113
|
-
successResultText: 'Operation completed.'
|
|
114
|
-
data: Dictionary new
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
By default, tools outside the MCP package are not auto-registered. If your
|
|
118
|
-
package intentionally publishes a tool whenever it is loaded, opt in:
|
|
119
|
-
|
|
120
|
-
```smalltalk
|
|
121
|
-
MyProjectMCPTool class >> shouldAutoRegister
|
|
122
|
-
|
|
123
|
-
^ true
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Auto-registered external tools are discoverable by default. Agents can find
|
|
127
|
-
them with `tool_search`, inspect them with `tool_get`, and call them by name.
|
|
128
|
-
|
|
129
|
-
`MCPToolRegistry` is the server's source of truth for registered tools. In
|
|
130
|
-
normal use, configure it through the `MCP` instance messages below. The
|
|
131
|
-
`tools/list` response contains only the static tool surface cached by the MCP
|
|
132
|
-
instance; discoverable tools remain callable, and are found with `tool_search`
|
|
133
|
-
and `tool_get`.
|
|
134
|
-
|
|
135
|
-
Two words govern the surface. A **registered** tool is in the catalog, found
|
|
136
|
-
with `tool_search` and `tool_get`, and callable. An **advertised** tool is also
|
|
137
|
-
in `tools/list`. Advertising decides what a client is shown, not what it may
|
|
138
|
-
call.
|
|
139
|
-
|
|
140
|
-
For a server with only your own tools:
|
|
141
|
-
|
|
142
|
-
```smalltalk
|
|
143
|
-
mcp := MCP onlyTools: { MyProjectMCPTool }.
|
|
144
|
-
mcp start.
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
`onlyTools:` replaces the catalog with exactly those tools and advertises all of
|
|
148
|
-
them. Advertise a subset when some registered tools should stay discoverable
|
|
149
|
-
through the catalog tools instead:
|
|
150
|
-
|
|
151
|
-
```smalltalk
|
|
152
|
-
mcp := MCP onlyTools: { MyProjectMCPTool. MyOtherMCPTool }.
|
|
153
|
-
mcp advertiseTools: { MyProjectMCPTool }.
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
To add to the tools a server already has, rather than replacing them:
|
|
157
|
-
|
|
158
|
-
```smalltalk
|
|
159
|
-
mcp := MCP new.
|
|
160
|
-
mcp addTool: MyProjectMCPTool.
|
|
161
|
-
mcp start.
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
`addTool:` takes a class or a ready instance, and `addTools:` takes several.
|
|
165
|
-
`removeTool:` takes one back out by name. `advertiseTools:` accepts classes,
|
|
166
|
-
instances or names, and registers anything not in the catalog yet, so it can be
|
|
167
|
-
called before or after adding. `advertiseAllTools` advertises everything, and
|
|
168
|
-
`advertiseToolDefaults` returns to each tool's own default exposure.
|
|
169
|
-
|
|
170
|
-
Use `defaultExposure` to decide whether a registered tool is directly
|
|
171
|
-
advertised in `tools/list` (`'static'`) or kept discoverable through the catalog
|
|
172
|
-
tools (`'discoverable'`, the default).
|
|
82
|
+
An image can serve tools, resources and prompts a project defines itself,
|
|
83
|
+
from that project's own package and without changing MCP. Read
|
|
84
|
+
[Building on MCP](building-on-mcp.md) for the server-only load, what a tool,
|
|
85
|
+
a resource and a prompt each need, and how the surface a client sees is
|
|
86
|
+
shaped.
|
|
@@ -31,6 +31,8 @@ guessing from source.
|
|
|
31
31
|
`skills/pharo-mcp-recovery/SKILL.md`.
|
|
32
32
|
- Before debugger sessions or repairs: read the local
|
|
33
33
|
[debugging guide](pharo-references/debugging.md).
|
|
34
|
+
- Before giving a project MCP tools, resources or prompts of its own: read
|
|
35
|
+
[building on MCP](pharo-references/building-on-mcp.md).
|
|
34
36
|
|
|
35
37
|
## Workflow
|
|
36
38
|
|