design-mcp-server — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited design-mcp-server (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Do NOT use for single-tool additions — use add-tool directly.
Gather before designing. Ask the user if not obvious from context:
If the domain has a public API, read its docs before designing. Don't design from vibes.
Before designing, verify the APIs and services the server will wrap.
If the Agent tool is available, spawn background agents to research in parallel while you proceed with domain mapping:
If the Agent tool is not available, do this research inline — fetch docs, read SDK readmes, confirm assumptions before committing them to the design.
List the concrete operations the underlying system supports. Group by domain noun.
Example for a project management API:
| Noun | Operations |
|---|---|
| Project | list, get, create, archive |
| Task | list (by project), get, create, update status, assign, comment |
| User | list, get current |
This is the raw material. Not everything becomes a tool.
| Primitive | Use when | Examples |
|---|---|---|
| Tool | Needs parameters beyond a simple ID, has side effects, or requires LLM decisions about inputs | Search, create, update, analyze, transform |
| Resource | Addressable by stable URI, read-only, useful as injectable context | Config, schemas, status, entity-by-ID lookups |
| Prompt | Reusable message template that structures how the LLM approaches a task | Analysis framework, report template, review checklist |
| Neither | Internal detail, admin-only, not useful to an LLM | Token refresh, webhook setup, migrations |
Common traps:
operation/mode parameter (see Step 4).This is the highest-leverage step. Tool definitions — names, descriptions, parameters, output schemas — are the entire interface contract the LLM reads to decide whether and how to call a tool. Every field is context. Design accordingly.
#### Think in workflows, not endpoints
The unit of a tool is a useful action, not an API call. Ask: "What is the agent trying to accomplish?" — not "What endpoints does the API have?"
A single tool can call multiple APIs internally, apply local filtering, reshape data, and return enriched results. The LLM doesn't know or care about the underlying calls.
Consolidation via operation/mode enum. When a domain noun has several related operations that share parameters, consolidate into one tool with a discriminated parameter. This keeps the tool surface small and lets the LLM discover all capabilities in one place.
// One tool for all branch operations — not five separate tools
const gitBranch = tool('git_branch', {
description: 'Manage branches: list, show current, create, delete, or rename.',
input: z.object({
operation: z.enum(['list', 'create', 'delete', 'rename', 'show-current'])
.describe('Branch operation to perform.'),
name: z.string().optional().describe('Branch name (required for create/delete/rename).'),
newName: z.string().optional().describe('New name (required for rename).'),
}),
output: z.object({ /* branch info */ }),
// ...
});// Workflow tool — search + local filter pipeline, not a raw API proxy
const findEligibleStudies = tool('clinicaltrials_find_eligible_studies', {
description: 'Matches patient demographics and medical profile to eligible clinical trials. '
+ 'Filters by age, sex, conditions, location, and healthy volunteer status. '
+ 'Returns ranked list of matching studies with eligibility explanations.',
// handler: listStudies() → filter by eligibility → rank by location proximity → slice
});When to consolidate vs. split:
| Consolidate (one tool) | Split (separate tools) |
|---|---|
| Operations share the same noun and most parameters | Operations have fundamentally different inputs/outputs |
| Related CRUD on a single entity | Read-only lookup vs. multi-step workflow |
| Agent would naturally think of them together | Agent would use them in different contexts |
There is no fixed ceiling on tool count — tools need to earn their keep, but don't artificially limit the surface. If the domain genuinely has 20 distinct workflows, expose 20 tools.
#### Tool descriptions
The description is the LLM's primary signal for tool selection. It must answer: what does this do, and when should I use it?
// Good — describes a prerequisite the LLM must know
description: 'Set the session working directory for all git operations. '
+ 'This allows subsequent git commands to omit the path parameter.'
// Good — self-explanatory, no workflow hints needed
description: 'Show the working tree status including staged, unstaged, and untracked files.'
// Good — warns about constraints
description: 'Fetches trial results data for completed studies. '
+ 'Only available for studies where hasResults is true.'Context-dependent: a simple read-only tool needs a one-line description. A tool with prerequisites, modes, or non-obvious behavior needs more. Match depth of description to complexity of tool.
#### Parameter descriptions
Every .describe() is prompt text the LLM reads. Parameters should convey: what the value is, what it affects, and (where non-obvious) how to use it well.
tools/list. Types like z.custom(), z.date(), z.transform(), z.bigint(), z.symbol(), z.void(), z.map(), z.set() throw at runtime. Use structural equivalents (e.g., z.string().describe('ISO 8601 date') instead of z.date()).// Good — explains cost, recommends action, names the alternative
fields: z.array(z.string()).optional()
.describe('Specific fields to return (reduces payload size). '
+ 'STRONGLY RECOMMENDED — without this, the full study record (~70KB each) is returned. '
+ 'Use full data only when you need detailed eligibility criteria, locations, or results.'),
// Good — explains what the flag does AND how to override
autoExclude: z.boolean().default(true)
.describe('Automatically exclude lock files and generated files from diff output '
+ 'to reduce context bloat. Set to false if you need to inspect these files.'),
// Good — names the format and gives one example
nctIds: z.union([z.string(), z.array(z.string()).max(5)])
.describe('A single NCT ID (e.g., "NCT12345678") or an array of up to 5 NCT IDs to fetch.'),#### Output design
The output schema and format function control what the LLM reads back. Design for the agent's next decision, not for a UI or an API consumer.
Principles:
git_commit auto-includes post-commit git status. The LLM sees the repo state without an extra round trip.)// git_diff — when lock files are filtered, the output tells the LLM
output: z.object({
diff: z.string().describe('Unified diff output.'),
excludedFiles: z.array(z.string()).optional()
.describe('Files automatically excluded from the diff (e.g., lock files). '
+ 'Call again with autoExclude=false to include them.'),
}),#### Error messages as LLM guidance
When a tool throws, the error message is the agent's only signal for recovery. A good error message tells the LLM what happened and what to do next.
// Bad — the LLM has no recovery path
throw new Error('Not found');
// Good — names both resolution options
"No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first."
// Good — structured hint in error data
throw new McpError(JsonRpcErrorCode.Forbidden,
"Cannot perform 'reset --hard' on protected branch 'main' without explicit confirmation.",
{ branch: 'main', operation: 'reset --hard', hint: 'Set the confirmed parameter to true to proceed.' },
);Think about the common failure modes for each tool and write error messages that guide recovery. This is part of the tool's interface design, not an afterthought.
#### Design table
Summarize each tool:
| Aspect | Decision |
|---|---|
| Name | snake_case, verb-noun: search_papers, create_task. Prefix with server domain if ambiguous. |
| Granularity | One tool per user-meaningful workflow, not per API call. Consolidate related operations with operation/mode enum. |
| Description | Concrete capability statement. Add operational guidance (prerequisites, constraints, gotchas) when non-obvious. |
| Input schema | .describe() on every field. Constrained types (enums, literals, regex). Explain costs/tradeoffs of parameter choices. |
| Output schema | Designed for the LLM's next action. Include chaining IDs. Communicate filtering. Post-write state where useful. |
| Error messages | Name what went wrong and what the LLM should do about it. Include hints for common recovery paths. |
| Annotations | readOnlyHint, destructiveHint, idempotentHint, openWorldHint. Helps clients auto-approve safely. |
| Auth scopes | tool:noun:read, tool:noun:write. Skip for read-only or stdio-only servers. |
For each resource:
| Aspect | Decision |
|---|---|
| URI template | scheme://{param}/path. Server domain as scheme. Keep shallow. |
| Params | Minimal — typically just an identifier. Complex queries belong in tools. |
| Pagination | Needed if lists exceed ~50 items. Opaque cursors via extractCursor/paginateArray. |
| list() | Provide if discoverable. Top-level categories or recent items, not exhaustive dumps. |
Optional. Use when the server has recurring interaction patterns worth structuring:
Skip for purely data/action-oriented servers.
Services — one per external dependency. Init/accessor pattern. Skip if all tools are thin wrappers with no shared state.
Config — list env vars (API keys, base URLs). Goes in src/config/server-config.ts as a separate Zod schema.
Create docs/design.md with the structure below. The MCP surface (tools, resources, prompts) goes first — it's what matters most and what the developer will reference during implementation.
# {{Server Name}} — Design
## MCP Surface
### Tools
| Name | Description | Key Inputs | Annotations |
|:-----|:------------|:-----------|:------------|
### Resources
| URI Template | Description | Pagination |
|:-------------|:------------|:-----------|
### Prompts
| Name | Description | Args |
|:-----|:------------|:-----|
## Overview
What this server does, what system it wraps, who it's for.
## Requirements
- Bullet list of capabilities and constraints
- Auth requirements, rate limits, data access scope
## Services
| Service | Wraps | Used By |
|:--------|:------|:--------|
## Config
| Env Var | Required | Description |
|:--------|:---------|:------------|
## Implementation Order
1. Config and server setup
2. Services (external API clients)
3. Read-only tools
4. Write tools
5. Resources
6. Prompts
Each step is independently testable.Keep it concise. The design doc is a working reference, not a spec document — enough to orient a developer (or agent) implementing the server, not more.
If the user has already authorized implementation (e.g., "build me a ___ server"), proceed directly to scaffolding using the design doc as the plan. Otherwise, present the design doc to the user for review before implementing.
Execute the plan using the scaffolding skills:
add-service for each serviceadd-tool for each tooladd-resource for each resourceadd-prompt for each promptdevcheck after each addition.describe() text explains what the value is, what it affects, and tradeoffsreadOnlyHint, destructiveHint, etc.){param} templates, pagination planned for large listsdocs/design.md~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.