Plugin structure and testing changedplugins/build
Nearest release: v2.1.284, published 16 hours after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 28 Sep 2026 00:41 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:07 UTC.
Upstream edited
Recorded here
Lines+143added
Lines−510removed
From line
2
where the diff opens
First seen
25 Sep 2026
this site's first read of the page
Recorded edits5to this page, all time
The whole hunk
from line 2, old and new numbered
/
The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.
from line 2
22
33> Reference for the plugin folder: what each file is, the manifest fields every app reads, bundling an MCP connector with its skill, and testing on each app.
44
5export const Piece = ({id, children}) => <div className="pe-piece" data-piece={id}>{children}</div>;
6
7export const PluginExplorer = ({children, variant}) => {
8 const SKILL_PIECES = [{
9 id: 'skillmd',
10 required: 'Required',
11 name: 'SKILL.md',
12 path: 'brand-guidelines/SKILL.md',
13 lines: [{
14 depth: 0,
15 kind: 'file',
16 text: 'SKILL.md'
17 }],
18 href: 'https://agentskills.io/specification',
19 linkText: 'Go to the Agent Skills specification'
20 }, {
21 id: 'references',
22 name: 'Reference file',
23 path: 'brand-guidelines/references/voice-and-tone.md',
24 lines: [{
25 depth: 0,
26 kind: 'folder',
27 text: 'references/'
28 }, {
29 depth: 1,
30 kind: 'file',
31 text: 'voice-and-tone.md'
32 }],
33 href: 'https://agentskills.io/specification',
34 linkText: 'Go to the Agent Skills specification'
35 }, {
36 id: 'assets',
37 name: 'Asset',
38 path: 'brand-guidelines/assets/slide-template.md',
39 lines: [{
40 depth: 0,
41 kind: 'folder',
42 text: 'assets/'
43 }, {
44 depth: 1,
45 kind: 'file',
46 text: 'slide-template.md'
47 }],
48 href: 'https://agentskills.io/specification',
49 linkText: 'Go to the Agent Skills specification'
50 }, {
51 id: 'scripts',
52 name: 'Script',
53 path: 'brand-guidelines/scripts/check_contrast.py',
54 lines: [{
55 depth: 0,
56 kind: 'folder',
57 text: 'scripts/'
58 }, {
59 depth: 1,
60 kind: 'file',
61 text: 'check_contrast.py'
62 }],
63 href: 'https://code.claude.com/docs/en/skills',
64 linkText: 'Go to Skills in Claude Code'
65 }];
66 const PLUGIN_PIECES = [{
67 id: 'manifest',
68 required: 'Required',
69 name: 'Manifest',
70 path: '.claude-plugin/plugin.json',
71 lines: [{
72 depth: 0,
73 kind: 'folder',
74 text: '.claude-plugin/'
75 }, {
76 depth: 1,
77 kind: 'file',
78 text: 'plugin.json'
79 }],
80 href: '/plugins/build#write-the-manifest',
81 linkText: 'Go to Write the manifest'
82 }, {
83 id: 'skills',
84 name: 'Skill',
85 path: 'skills/file-expense/SKILL.md',
86 lines: [{
87 depth: 0,
88 kind: 'folder',
89 text: 'skills/'
90 }, {
91 depth: 1,
92 kind: 'folder',
93 text: 'file-expense/'
94 }, {
95 depth: 2,
96 kind: 'file',
97 text: 'SKILL.md'
98 }],
99 href: '/skills/how-to',
100 linkText: 'Go to Create custom skills'
101 }, {
102 id: 'references',
103 name: 'Skill reference file',
104 path: 'skills/file-expense/references/categories.md',
105 lines: [{
106 depth: 2,
107 kind: 'folder',
108 text: 'references/'
109 }, {
110 depth: 3,
111 kind: 'file',
112 text: 'categories.md'
113 }],
114 href: '/skills/how-to#add-resources',
115 linkText: 'Go to Add resources'
116 }, {
117 id: 'scripts',
118 name: 'Skill script',
119 path: 'skills/file-expense/scripts/total.py',
120 lines: [{
121 depth: 2,
122 kind: 'folder',
123 text: 'scripts/'
124 }, {
125 depth: 3,
126 kind: 'file',
127 text: 'total.py'
128 }],
129 href: '/skills/how-to#add-scripts',
130 linkText: 'Go to Add scripts'
131 }, {
132 id: 'commands',
133 name: 'Command',
134 path: 'commands/summarize.md',
135 lines: [{
136 depth: 0,
137 kind: 'folder',
138 text: 'commands/'
139 }, {
140 depth: 1,
141 kind: 'file',
142 text: 'summarize.md'
143 }],
144 href: '/plugins/platform-support#compare-component-support-by-app',
145 linkText: 'Go to component support by app'
146 }, {
147 id: 'mcp',
148 name: 'MCP connector',
149 path: '.mcp.json',
150 lines: [{
151 depth: 0,
152 kind: 'file',
153 text: '.mcp.json'
154 }],
155 href: '/plugins/build#bundle-an-mcp-connector-with-its-skill',
156 linkText: 'Go to Bundle an MCP connector with its skill'
157 }, {
158 id: 'readme',
159 required: 'Required to publish',
160 name: 'README',
161 path: 'README.md',
162 lines: [{
163 depth: 0,
164 kind: 'file',
165 text: 'README.md'
166 }],
167 href: '/plugins/pre-submission-checklist#readme-and-license',
168 linkText: 'Go to README and license checks'
169 }, {
170 id: 'license',
171 required: 'Required to publish',
172 name: 'License',
173 path: 'LICENSE',
174 lines: [{
175 depth: 0,
176 kind: 'file',
177 text: 'LICENSE'
178 }],
179 href: '/plugins/pre-submission-checklist#readme-and-license',
180 linkText: 'Go to README and license checks'
181 }];
182 const isSkill = variant === 'skill';
183 const PIECES = isSkill ? SKILL_PIECES : PLUGIN_PIECES;
184 const rootLabel = isSkill ? 'brand-guidelines/' : 'expense-reports/';
185 const title = isSkill ? 'What goes in the skill folder' : 'What goes in the plugin folder';
186 const treeCaption = isSkill ? 'Skill folder' : 'Plugin folder';
187 const [selectedId, setSelectedId] = useState(isSkill ? 'skillmd' : 'manifest');
188 const [isFullscreen, setIsFullscreen] = useState(false);
189 const rootRef = useRef(null);
190 useEffect(() => {
191 const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
192 document.addEventListener('fullscreenchange', onFsChange);
193 return () => document.removeEventListener('fullscreenchange', onFsChange);
194 }, []);
195 const toggleFullscreen = () => {
196 if (!rootRef.current) return;
197 if (document.fullscreenElement) document.exitFullscreen(); else rootRef.current.requestFullscreen().catch(() => {});
198 };
199 const selected = PIECES.find(p => p.id === selectedId) || PIECES[0];
200 const onTreeKeyDown = e => {
201 const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
202 if (keys.indexOf(e.key) === -1) return;
203 const i = PIECES.findIndex(p => p.id === selectedId);
204 let next = i;
205 if (e.key === 'ArrowDown') next = Math.min(PIECES.length - 1, i + 1);
206 if (e.key === 'ArrowUp') next = Math.max(0, i - 1);
207 if (e.key === 'Home') next = 0;
208 if (e.key === 'End') next = PIECES.length - 1;
209 e.preventDefault();
210 if (next === i) return;
211 const id = PIECES[next].id;
212 setSelectedId(id);
213 const el = document.getElementById('pe-node-' + id);
214 if (el) el.focus();
215 };
216 const FolderIcon = () => <svg className="pe-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" aria-hidden="true">
217 <path d="M1.5 4.5a1 1 0 0 1 1-1h3.2l1.3 1.5h6a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1h-10.5a1 1 0 0 1-1-1z" />
218 </svg>;
219 const FileIcon = () => <svg className="pe-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" aria-hidden="true">
220 <path d="M4 1.5h5.5L13 5v9.5H4z" />
221 <path d="M9.5 1.5V5H13" />
222 </svg>;
223 return <div ref={rootRef} className={isFullscreen ? 'pe-root pe-fullscreen not-prose' : 'pe-root not-prose'} data-selected={selected.id}>
224 <style>{`
225 .pe-root {
226 --pe-mono: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
227 --pe-accent: #D97757;
228 --pe-accent-text: #A8502F;
229 --pe-accent-bg: rgba(217,119,87,0.10);
230 --pe-bg: #FFFFFF;
231 --pe-surface: #FAFAF7;
232 --pe-hover: #F0EEE6;
233 --pe-border: #E8E6DC;
234 --pe-text: #141413;
235 --pe-text-2: #3D3D3A;
236 --pe-text-3: #5E5D59;
237 font-family: inherit;
238 background: var(--pe-bg);
239 color: var(--pe-text);
240 border: 1px solid var(--pe-border);
241 border-radius: 12px;
242 margin: 1.5rem 0;
243 overflow: hidden;
244 box-sizing: border-box;
245 }
246 .dark .pe-root {
247 --pe-accent-text: #EBA98F;
248 --pe-accent-bg: rgba(217,119,87,0.18);
249 --pe-bg: #1A1918;
250 --pe-surface: #232221;
251 --pe-hover: #2E2D2B;
252 --pe-border: #3A3936;
253 --pe-text: #F1EFE9;
254 --pe-text-2: #D6D4CA;
255 --pe-text-3: #B8B5AD;
256 }
257 .pe-root *, .pe-root *::before, .pe-root *::after { box-sizing: border-box; }
258 .pe-head { display: flex; align-items: flex-start; gap: 12px; padding: 18px 24px 16px; border-bottom: 1px solid var(--pe-border); }
259 .pe-head-text { flex: 1; min-width: 0; }
260 .pe-fs-btn { flex-shrink: 0; width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--pe-border); border-radius: 6px; background: var(--pe-surface); color: var(--pe-text-2); font-size: 15px; line-height: 1; cursor: pointer; }
261 .pe-fs-btn:hover { background: var(--pe-hover); }
262 .pe-fs-btn:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: 2px; }
263 .pe-fullscreen { border-radius: 0; height: 100vh; display: flex; flex-direction: column; overflow: auto; }
264 .pe-fullscreen .pe-body { flex: 1; }
265 .pe-title { font-size: 19px; font-weight: 600; line-height: 1.3; color: var(--pe-text); margin: 0; }
266 .pe-sub { font-size: 15px; line-height: 1.5; color: var(--pe-text-3); margin: 4px 0 0; }
267 .pe-sub code { font-family: var(--pe-mono); font-size: 0.88em; padding: 1px 5px; border-radius: 4px; background: var(--pe-surface); border: 1px solid var(--pe-border); }
268 .pe-body { display: flex; align-items: stretch; }
269 .pe-tree-pane { width: 270px; flex-shrink: 0; background: var(--pe-surface); border-right: 1px solid var(--pe-border); padding: 16px 0 12px; }
270 .pe-panel { flex: 1; min-width: 0; padding: 16px 24px 24px; }
271 .pe-caption { font-size: 13px; font-weight: 600; color: var(--pe-text-3); margin: 0 0 10px; }
272 .pe-tree-pane .pe-caption { padding: 0 16px; }
273 .pe-rootline { display: flex; align-items: center; gap: 7px; padding: 3px 16px; font-family: var(--pe-mono); font-size: 13.5px; color: var(--pe-text-3); }
274 .pe-node {
275 display: block; width: 100%; margin: 0; padding: 3px 16px 3px 30px; text-align: left; cursor: pointer;
276 background: transparent; color: var(--pe-text-2);
277 border: none; border-left: 3px solid transparent;
278 font-family: var(--pe-mono); font-size: 13.5px; line-height: 1.4;
279 }
280 .pe-node:hover { background: var(--pe-hover); }
281 .pe-node:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: -2px; }
282 .pe-node[aria-pressed="true"] { background: var(--pe-accent-bg); border-left-color: var(--pe-accent); color: var(--pe-accent-text); font-weight: 600; }
283 .pe-line { display: flex; align-items: center; gap: 7px; padding: 2px 0; }
284 .pe-line span { overflow-wrap: anywhere; }
285 .pe-line > span:not(.pe-req) { white-space: nowrap; flex-shrink: 0; }
286 .pe-req { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; margin-left: 8px; padding: 0 6px; border-radius: 999px; font-size: 11px; line-height: 18px; font-family: var(--pe-sans, inherit); letter-spacing: .02em; color: var(--pe-accent-text); border: 1px solid var(--pe-border); background: var(--pe-surface); white-space: nowrap; }
287 .pe-piece { display: none; font-size: 16px; line-height: 1.6; color: var(--pe-text-2); }
288 ${PIECES.map(p => '.pe-root[data-selected="' + p.id + '"] .pe-piece[data-piece="' + p.id + '"]').join(',\n ')} { display: block; }
289 .pe-piece p { margin: 0 0 10px; }
290 .pe-piece p:last-child { margin-bottom: 0; }
291 .pe-piece ul { list-style: disc; padding-left: 1.25em; margin: 0 0 10px; }
292 .pe-piece li { margin: 2px 0; }
293 .pe-piece code { font-family: var(--pe-mono); font-size: 0.88em; padding: 1px 5px; border-radius: 4px; background: var(--pe-surface); border: 1px solid var(--pe-border); }
294 .pe-piece .code-block { margin: 12px 0 0; }
295 .pe-piece pre code { padding: 0; border: none; background: none; }
296 .pe-piece a { color: var(--pe-accent-text); }
297 .pe-line-compact { display: none; }
298 .pe-icon { flex-shrink: 0; }
299 .pe-name { font-size: 22px; font-weight: 600; line-height: 1.25; letter-spacing: -0.2px; color: var(--pe-text); margin: 0; }
300 .pe-path { font-family: var(--pe-mono); font-size: 13.5px; color: var(--pe-accent-text); margin: 4px 0 0; overflow-wrap: anywhere; }
301 .pe-block { margin: 20px 0 0; }
302 .pe-link {
303 display: inline-block; margin: 24px 0 0; padding: 8px 14px; border-radius: 8px;
304 font-size: 14.5px; font-weight: 600; text-decoration: none;
305 color: var(--pe-accent-text); background: var(--pe-accent-bg); border: 1px solid var(--pe-accent);
306 }
307 .pe-link:hover { filter: brightness(0.97); }
308 .pe-link:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: 2px; }
309 @media (max-width: 700px) {
310 .pe-head { padding: 16px 16px 14px; }
311 .pe-body { flex-direction: column; }
312 .pe-tree-pane { width: 100%; border-right: none; border-bottom: 1px solid var(--pe-border); }
313 .pe-line-tree { display: none; }
314 .pe-line-compact { display: flex; }
315 .pe-panel { padding: 16px 16px 20px; }
316 }
317 `}</style>
318
319 <div className="pe-head">
320 <div className="pe-head-text">
321 <div className="pe-title">{title}</div>
322 {isSkill ? <div className="pe-sub">This example skill, <code>brand-guidelines</code>, has one of each kind of file a skill can carry. Select a file to read what it's for and see a minimal example.</div> : <div className="pe-sub">This example plugin, <code>expense-reports</code>, has one of each file that chat, Cowork, and Claude Code all load, plus the README and license the directory requires. Select a file to read what it’s for and see a minimal example.</div>}
323 </div>
324 <button type="button" className="pe-fs-btn" onClick={toggleFullscreen} aria-label={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'} title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}>
325 {isFullscreen ? '⤡' : '⛶'}
326 </button>
327 </div>
328
329 <div className="pe-body">
330 <div className="pe-tree-pane">
331 <div className="pe-caption" id="pe-tree-caption">{treeCaption}</div>
332 <div role="group" aria-labelledby="pe-tree-caption" onKeyDown={onTreeKeyDown}>
333 <div className="pe-rootline"><FolderIcon /><span>{rootLabel}</span></div>
334 {PIECES.map(p => <button key={p.id} id={'pe-node-' + p.id} type="button" className="pe-node" aria-pressed={p.id === selected.id} aria-label={p.name + ', ' + p.path} onClick={() => setSelectedId(p.id)}>
335 {p.lines.map((line, i) => <span key={i} className="pe-line pe-line-tree" style={{
336 paddingLeft: line.depth * 18 + 'px'
337 }}>
338 {line.kind === 'folder' ? <FolderIcon /> : <FileIcon />}
339 <span>{line.text}</span>
340 {p.required && i === p.lines.length - 1 ? <span className="pe-req" title={p.required}>Required</span> : null}
341 </span>)}
342 <span className="pe-line pe-line-compact">
343 <FileIcon />
344 <span>{p.path}</span>
345 {p.required ? <span className="pe-req" title={p.required}>Required</span> : null}
346 </span>
347 </button>)}
348 </div>
349 </div>
350
351 <div className="pe-panel" role="region" aria-labelledby="pe-panel-caption" aria-live="polite" aria-atomic="true">
352 <div className="pe-caption" id="pe-panel-caption">Selected file</div>
353 <div className="pe-name">{selected.name}{selected.required ? <span className="pe-req">{selected.required}</span> : null}</div>
354 <div className="pe-path">{selected.path}</div>
355
356 <div className="pe-block">{children}</div>
357
358 <a className="pe-link" href={selected.href}>{selected.linkText}</a>
359 </div>
360 </div>
361 </div>;
362};
363
3645A plugin is a folder that packages skills, MCP connectors, commands, and agents, in any combination, so that people add them together. This page is the reference for that folder: what each file is and contains, the manifest fields every app reads, how an MCP connector and its skill fit together, and how to test the plugin on claude.ai, in Cowork, and in Claude Code.
3656
3667[Build your first plugin](/docs/plugins/quickstart) walks you through making one end to end.
3678
368<Note>
369 * If you want a plugin for your own use without writing files, see [Create a plugin with Claude](/docs/plugins/create-with-claude), which makes one from a conversation
370 * If you're building for Claude Code only, see its [plugin authoring docs](https://code.claude.com/docs/en/plugins/create). Claude Code supports more component types than the other surfaces, and those docs are the full reference for anything Claude Code-only
371 * If you're deciding which pieces your plugin needs, see [Decide what to include in your plugin](/docs/connectors/building/what-to-build)
372</Note>
9> [!NOTE]
10>
11> * If you want a plugin for your own use without writing files, see [Create a plugin with Claude](/docs/plugins/create-with-claude), which makes one from a conversation
12> * If you're building for Claude Code only, see its [plugin authoring docs](https://code.claude.com/docs/en/plugins/create). Claude Code supports more component types than the other surfaces, and those docs are the full reference for anything Claude Code-only
13> * If you're deciding which pieces your plugin needs, see [Decide what to include in your plugin](/docs/connectors/building/what-to-build)
37314
37415## Lay out the plugin folder
37516
from line 22
38122
38223Select a file to see what it's for and a minimal example; components that only some apps load, such as agents and hooks, are listed after the explorer.
38324
384<PluginExplorer>
385 <Piece id="manifest">
386 <p>The manifest identifies the plugin to every app and to Anthropic's directory. Without any `plugin.json`, the directory accepts only a folder whose `skills/` directory holds at least one `<name>/SKILL.md`, and lists that plugin for Claude Code only, not for claude.ai or Cowork. To publish the plugin in Anthropic's directory you also need [a README and a license](/docs/plugins/pre-submission-checklist#readme-and-license); select those files in the tree to see what each needs.</p>
387
388 <p>Its `name` is the plugin's permanent identity: people install and refer to the plugin by this value, so make it specific to your product and never change it after release. Change `displayName` when you want a different label.</p>
389
390 <p>This manifest has the fields that every app and the directory read:</p>
391
392 ```json theme={null}
393 {
394 "name": "expense-reports",
395 "displayName": "Expense Reports",
396 "version": "1.0.0",
397 "description": "File, track, and approve expense reports from a conversation, using your finance system's connector and your company's approval rules.",
398 "author": { "name": "Example Corp", "url": "https://example.com" },
399 "license": "MIT"
25What goes in the plugin folder
26
27This example plugin, `expense-reports`, has one of each file that chat, Cowork, and Claude Code all load, plus the README and license the directory requires. Select a file to read what it’s for and see a minimal example.
28
29Plugin folder
30
31expense-reports/
32
33Selected file
34
35The manifest identifies the plugin to every app and to Anthropic's directory. Without any `plugin.json`, the directory accepts only a folder whose `skills/` directory holds at least one `<name>/SKILL.md`, and lists that plugin for Claude Code only, not for claude.ai or Cowork. To publish the plugin in Anthropic's directory you also need [a README and a license](/docs/plugins/pre-submission-checklist#readme-and-license); select those files in the tree to see what each needs.
36
37Its `name` is the plugin's permanent identity: people install and refer to the plugin by this value, so make it specific to your product and never change it after release. Change `displayName` when you want a different label.
38
39This manifest has the fields that every app and the directory read:
40
41```json theme={null}
42{
43 "name": "expense-reports",
44 "displayName": "Expense Reports",
45 "version": "1.0.0",
46 "description": "File, track, and approve expense reports from a conversation, using your finance system's connector and your company's approval rules.",
47 "author": { "name": "Example Corp", "url": "https://example.com" },
48 "license": "MIT"
49}
50```
51
52A skill is a `SKILL.md` file in its own folder under `skills/`, and the folder name matches the skill's `name` field. Claude decides when to load the skill from its `description`, so write the description as the situations a user would be in. Skills load in chat, Cowork, and Claude Code.
53
54This skill files an expense from a receipt:
55
56```markdown theme={null}
57---
58name: file-expense
59description: File an expense report. Use when the user mentions a receipt, reimbursement, or expense, or asks to submit spending for approval.
60---
61
62To file an expense:
63
641. Read the amount, date, merchant, and currency from the receipt.
652. Call the expenses connector's `create_report` tool with those fields.
663. Reply with the report number and its approval status.
67```
68
69A skill folder can hold [supporting files](/docs/skills/how-to#add-resources) that are too detailed for `SKILL.md`:
70
71<ul>
72 <li>`references/` for documentation Claude reads when needed</li>
73 <li>`assets/` for templates, images, and data files</li>
74 <li>`scripts/` for code</li>
75</ul>
76
77Mention each file in `SKILL.md` so Claude knows when to load it, for example "Pick the category from `references/categories.md`".
78
79This reference file lists the expense categories:
80
81```markdown theme={null}
82# Expense categories
83
84- Travel: flights, hotels, taxis, and mileage
85- Meals: client meals and team events
86- Software: subscriptions and licenses
87```
88
89A skill can include scripts in a `scripts/` folder next to `SKILL.md`.
90
91In `SKILL.md`, refer to a script by its path under `${CLAUDE_SKILL_DIR}`, for example `python3 ${CLAUDE_SKILL_DIR}/scripts/total.py receipts.csv`. [Add scripts](/docs/skills/how-to#add-scripts) explains how each app resolves that path.
92
93This script totals the amounts in a CSV file of receipts:
94
95```python theme={null}
96import csv
97import sys
98
99with open(sys.argv[1], newline="") as f:
100 total = sum(float(row["amount"]) for row in csv.DictReader(f))
101print(f"{total:.2f}")
102```
103
104A command is a single Markdown file under `commands/` with a `description` in its frontmatter. In Cowork and Claude Code, you run it by typing its name, here `/expense-reports:summarize`. In chat, a command loads as a skill, and Claude applies it when the task fits.
105
106This command summarizes a month's expenses:
107
108```markdown theme={null}
109---
110description: Summarize this month's expense reports
111---
112
113List this month's expense reports with their totals and approval status, then summarize them in three sentences.
114```
115
116`.mcp.json` at the plugin root references your remote MCP server by URL. On claude.ai and in Cowork, the entry is listed on the plugin's **Connectors** tab, where each user adds or connects it and signs in through your server's OAuth flow. Claude Code loads it with the plugin.
117
118Don't put API keys or other secrets in this file, because every person who installs the plugin receives its files.
119
120This one points at the expenses server:
121
122```json theme={null}
123{
124 "mcpServers": {
125 "expenses": {
126 "type": "http",
127 "url": "https://mcp.example.com/mcp"
400128 }
401 ```
402 </Piece>
403
404 <Piece id="skills">
405 <p>A skill is a `SKILL.md` file in its own folder under `skills/`, and the folder name matches the skill's `name` field. Claude decides when to load the skill from its `description`, so write the description as the situations a user would be in. Skills load in chat, Cowork, and Claude Code.</p>
406
407 <p>This skill files an expense from a receipt:</p>
408
409 ```markdown theme={null}
410 ---
411 name: file-expense
412 description: File an expense report. Use when the user mentions a receipt, reimbursement, or expense, or asks to submit spending for approval.
413 ---
414
415 To file an expense:
416
417 1. Read the amount, date, merchant, and currency from the receipt.
418 2. Call the expenses connector's `create_report` tool with those fields.
419 3. Reply with the report number and its approval status.
420 ```
421 </Piece>
422
423 <Piece id="references">
424 <p>A skill folder can hold [supporting files](/docs/skills/how-to#add-resources) that are too detailed for `SKILL.md`:</p>
425
426 <ul>
427 <li>`references/` for documentation Claude reads when needed</li>
428 <li>`assets/` for templates, images, and data files</li>
429 <li>`scripts/` for code</li>
430 </ul>
431
432 <p>Mention each file in `SKILL.md` so Claude knows when to load it, for example "Pick the category from `references/categories.md`".</p>
433
434 <p>This reference file lists the expense categories:</p>
435
436 ```markdown theme={null}
437 # Expense categories
438
439 - Travel: flights, hotels, taxis, and mileage
440 - Meals: client meals and team events
441 - Software: subscriptions and licenses
442 ```
443 </Piece>
444
445 <Piece id="scripts">
446 <p>A skill can include scripts in a `scripts/` folder next to `SKILL.md`.</p>
447
448 <p>In `SKILL.md`, refer to a script by its path under `${CLAUDE_SKILL_DIR}`, for example `python3 ${CLAUDE_SKILL_DIR}/scripts/total.py receipts.csv`. [Add scripts](/docs/skills/how-to#add-scripts) explains how each app resolves that path.</p>
449
450 <p>This script totals the amounts in a CSV file of receipts:</p>
451
452 ```python theme={null}
453 import csv
454 import sys
455
456 with open(sys.argv[1], newline="") as f:
457 total = sum(float(row["amount"]) for row in csv.DictReader(f))
458 print(f"{total:.2f}")
459 ```
460 </Piece>
461
462 <Piece id="commands">
463 <p>A command is a single Markdown file under `commands/` with a `description` in its frontmatter. In Cowork and Claude Code, you run it by typing its name, here `/expense-reports:summarize`. In chat, a command loads as a skill, and Claude applies it when the task fits.</p>
464
465 <p>This command summarizes a month's expenses:</p>
466
467 ```markdown theme={null}
468 ---
469 description: Summarize this month's expense reports
470 ---
471
472 List this month's expense reports with their totals and approval status, then summarize them in three sentences.
473 ```
474 </Piece>
475
476 <Piece id="mcp">
477 <p>`.mcp.json` at the plugin root references your remote MCP server by URL. On claude.ai and in Cowork, the entry is listed on the plugin's **Connectors** tab, where each user adds or connects it and signs in through your server's OAuth flow. Claude Code loads it with the plugin.</p>
478
479 <p>Don't put API keys or other secrets in this file, because every person who installs the plugin receives its files.</p>
480
481 <p>This one points at the expenses server:</p>
482
483 ```json theme={null}
484 {
485 "mcpServers": {
486 "expenses": {
487 "type": "http",
488 "url": "https://mcp.example.com/mcp"
489 }
490 }
491 }
492 ```
493 </Piece>
494
495 <Piece id="readme">
496 <p>A plugin loads without a README, but you need one to publish it: Anthropic's directory shows the README as the listing's description, and [validation](/docs/plugins/pre-submission-checklist#readme-and-license) blocks a submission that has none.</p>
497
498 <p>Write at least 40 words, not counting words inside code blocks, and say what the plugin does, how to use it, and what data it sends.</p>
499
500 <p>This README covers those three points:</p>
501
502 ```markdown theme={null}
503 # Expense Reports
504
505 File, track, and approve expense reports from a conversation with Claude.
506
507 ## Use it
508
509 Attach a receipt and ask Claude to file it. Claude reads the amount, date,
510 and merchant, files the report through the Expense Reports connector, and
511 replies with the report number. Ask what's waiting on you to list the
512 reports that need your approval.
513
514 ## Data
515
516 The plugin sends receipt details and report fields to your Example Corp
517 account through mcp.example.com. It stores nothing itself.
518 ```
519 </Piece>
520
521 <Piece id="license">
522 <p>A plugin loads without a license, but you need one to publish it: Anthropic's directory doesn't list a plugin until it has a license, and [validation](/docs/plugins/pre-submission-checklist#readme-and-license) blocks a submission that has neither a `LICENSE` file nor a `license` field.</p>
523
524 <p>Add a `LICENSE` file to the plugin folder, or set `license` in `plugin.json`.</p>
525 </Piece>
526</PluginExplorer>
527
528<Note>
529 The explorer is the scaled-down set: the pieces that chat, Cowork, and Claude Code all load, plus the README and license that Anthropic's directory requires. For every other folder and field a plugin can contain, including the pieces only Claude Code loads, see [Plugin components](https://code.claude.com/docs/en/plugins/components) and the [manifest reference](https://code.claude.com/docs/en/plugins/manifest-reference) in the Claude Code docs.
530</Note>
129 }
130}
131```
132
133A plugin loads without a README, but you need one to publish it: Anthropic's directory shows the README as the listing's description, and [validation](/docs/plugins/pre-submission-checklist#readme-and-license) blocks a submission that has none.
134
135Write at least 40 words, not counting words inside code blocks, and say what the plugin does, how to use it, and what data it sends.
136
137This README covers those three points:
138
139```markdown theme={null}
140# Expense Reports
141
142File, track, and approve expense reports from a conversation with Claude.
143
144## Use it
145
146Attach a receipt and ask Claude to file it. Claude reads the amount, date,
147and merchant, files the report through the Expense Reports connector, and
148replies with the report number. Ask what's waiting on you to list the
149reports that need your approval.
150
151## Data
152
153The plugin sends receipt details and report fields to your Example Corp
154account through mcp.example.com. It stores nothing itself.
155```
156
157A plugin loads without a license, but you need one to publish it: Anthropic's directory doesn't list a plugin until it has a license, and [validation](/docs/plugins/pre-submission-checklist#readme-and-license) blocks a submission that has neither a `LICENSE` file nor a `license` field.
158
159Add a `LICENSE` file to the plugin folder, or set `license` in `plugin.json`.
160
161> [!NOTE]
162>
163> The explorer is the scaled-down set: the pieces that chat, Cowork, and Claude Code all load, plus the README and license that Anthropic's directory requires. For every other folder and field a plugin can contain, including the pieces only Claude Code loads, see [Plugin components](https://code.claude.com/docs/en/plugins/components) and the [manifest reference](https://code.claude.com/docs/en/plugins/manifest-reference) in the Claude Code docs.
531164
532165### Check what each app loads
533166
534167
No line in this hunk matches that.