mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 04:24:17 +02:00
Merge branch 'main' into elicitation
This commit is contained in:
commit
11d2bacbb4
190 changed files with 14832 additions and 1888 deletions
13
.github/release.yml
vendored
13
.github/release.yml
vendored
|
|
@ -9,7 +9,7 @@ changelog:
|
|||
- feature
|
||||
exclude:
|
||||
labels:
|
||||
- breaking change
|
||||
- contrib
|
||||
|
||||
- title: Enhancements 🔧
|
||||
labels:
|
||||
|
|
@ -17,22 +17,31 @@ changelog:
|
|||
exclude:
|
||||
labels:
|
||||
- breaking change
|
||||
- contrib
|
||||
|
||||
- title: Fixes 🐞
|
||||
labels:
|
||||
- bug
|
||||
exclude:
|
||||
labels:
|
||||
- breaking change
|
||||
- contrib
|
||||
|
||||
- title: Breaking Changes 🛫
|
||||
labels:
|
||||
- breaking change
|
||||
exclude:
|
||||
labels:
|
||||
- contrib
|
||||
|
||||
- title: Docs 📚
|
||||
labels:
|
||||
- documentation
|
||||
|
||||
- title: Examples & Contrib 💡
|
||||
labels:
|
||||
- example
|
||||
- contrib
|
||||
|
||||
- title: Dependencies 📦
|
||||
labels:
|
||||
- dependencies
|
||||
|
|
|
|||
|
|
@ -32,4 +32,6 @@ async with Client(transport=StreamableHttpTransport(server_url)) as client:
|
|||
## Development Workflow
|
||||
|
||||
- You must always run pre-commit if you open a PR, because it is run as part of a required check.
|
||||
- When opening PRs, apply labels appropriately for bugs/breaking changes/enhancements/features. Generally, improvements are enhancements (not features) unless told otherwise.
|
||||
- When opening PRs, apply labels appropriately for bugs/breaking changes/enhancements/features. Generally, improvements are enhancements (not features) unless told otherwise.
|
||||
- NEVER modify files in docs/python-sdk/**, as they are auto-generated.
|
||||
- Use # type: ignore[attr-defined] in unit tests when accessing an MCP result of indeterminate type instead of asserting its type
|
||||
|
|
@ -349,7 +349,7 @@ mcp.run(transport="stdio") # Default, so transport argument is optional
|
|||
**Streamable HTTP**: Recommended for web deployments.
|
||||
|
||||
```python
|
||||
mcp.run(transport="streamable-http", host="127.0.0.1", port=8000, path="/mcp")
|
||||
mcp.run(transport="http", host="127.0.0.1", port=8000, path="/mcp")
|
||||
```
|
||||
|
||||
**SSE**: For compatibility with existing SSE clients.
|
||||
|
|
|
|||
364
docs/.cursor/rules/mintlify.mdc
Normal file
364
docs/.cursor/rules/mintlify.mdc
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
---
|
||||
description:
|
||||
globs: *.mdx
|
||||
alwaysApply: false
|
||||
---
|
||||
# Mintlify technical writing assistant
|
||||
|
||||
You are an AI writing assistant specialized in creating exceptional technical documentation using Mintlify components and following industry-leading technical writing practices.
|
||||
|
||||
## Core writing principles
|
||||
|
||||
### Language and style requirements
|
||||
- Use clear, direct language appropriate for technical audiences
|
||||
- Write in second person ("you") for instructions and procedures
|
||||
- Use active voice over passive voice
|
||||
- Employ present tense for current states, future tense for outcomes
|
||||
- Maintain consistent terminology throughout all documentation
|
||||
- Keep sentences concise while providing necessary context
|
||||
- Use parallel structure in lists, headings, and procedures
|
||||
|
||||
### Content organization standards
|
||||
- Lead with the most important information (inverted pyramid structure)
|
||||
- Use progressive disclosure: basic concepts before advanced ones
|
||||
- Break complex procedures into numbered steps
|
||||
- Include prerequisites and context before instructions
|
||||
- Provide expected outcomes for each major step
|
||||
- End sections with next steps or related information
|
||||
- Use descriptive, keyword-rich headings for navigation and SEO
|
||||
|
||||
### User-centered approach
|
||||
- Focus on user goals and outcomes rather than system features
|
||||
- Anticipate common questions and address them proactively
|
||||
- Include troubleshooting for likely failure points
|
||||
- Provide multiple pathways when appropriate (beginner vs advanced), but offer an opinionated path for people to follow to avoid overwhelming with options
|
||||
|
||||
## Mintlify component reference
|
||||
|
||||
### Callout components
|
||||
|
||||
#### Note - Additional helpful information
|
||||
|
||||
<Note>
|
||||
Supplementary information that supports the main content without interrupting flow
|
||||
</Note>
|
||||
|
||||
#### Tip - Best practices and pro tips
|
||||
|
||||
<Tip>
|
||||
Expert advice, shortcuts, or best practices that enhance user success
|
||||
</Tip>
|
||||
|
||||
#### Warning - Important cautions
|
||||
|
||||
<Warning>
|
||||
Critical information about potential issues, breaking changes, or destructive actions
|
||||
</Warning>
|
||||
|
||||
#### Info - Neutral contextual information
|
||||
|
||||
<Info>
|
||||
Background information, context, or neutral announcements
|
||||
</Info>
|
||||
|
||||
#### Check - Success confirmations
|
||||
|
||||
<Check>
|
||||
Positive confirmations, successful completions, or achievement indicators
|
||||
</Check>
|
||||
|
||||
### Code components
|
||||
|
||||
#### Single code block
|
||||
|
||||
```javascript config.js
|
||||
const apiConfig = {
|
||||
baseURL: 'https://api.example.com',
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.API_TOKEN}`
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### Code group with multiple languages
|
||||
|
||||
<CodeGroup>
|
||||
```javascript Node.js
|
||||
const response = await fetch('/api/endpoint', {
|
||||
headers: { Authorization: `Bearer ${apiKey}` }
|
||||
});
|
||||
```
|
||||
|
||||
```python Python
|
||||
import requests
|
||||
response = requests.get('/api/endpoint',
|
||||
headers={'Authorization': f'Bearer {api_key}'})
|
||||
```
|
||||
|
||||
```curl cURL
|
||||
curl -X GET '/api/endpoint' \
|
||||
-H 'Authorization: Bearer YOUR_API_KEY'
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Request/Response examples
|
||||
|
||||
<RequestExample>
|
||||
```bash cURL
|
||||
curl -X POST 'https://api.example.com/users' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name": "John Doe", "email": "john@example.com"}'
|
||||
```
|
||||
</RequestExample>
|
||||
|
||||
<ResponseExample>
|
||||
```json Success
|
||||
{
|
||||
"id": "user_123",
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"created_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
</ResponseExample>
|
||||
|
||||
### Structural components
|
||||
|
||||
#### Steps for procedures
|
||||
|
||||
<Steps>
|
||||
<Step title="Install dependencies">
|
||||
Run `npm install` to install required packages.
|
||||
|
||||
<Check>
|
||||
Verify installation by running `npm list`.
|
||||
</Check>
|
||||
</Step>
|
||||
|
||||
<Step title="Configure environment">
|
||||
Create a `.env` file with your API credentials.
|
||||
|
||||
```bash
|
||||
API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Never commit API keys to version control.
|
||||
</Warning>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
#### Tabs for alternative content
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
```bash
|
||||
brew install node
|
||||
npm install -g package-name
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Windows">
|
||||
```powershell
|
||||
choco install nodejs
|
||||
npm install -g package-name
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Linux">
|
||||
```bash
|
||||
sudo apt install nodejs npm
|
||||
npm install -g package-name
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
#### Accordions for collapsible content
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Troubleshooting connection issues">
|
||||
- **Firewall blocking**: Ensure ports 80 and 443 are open
|
||||
- **Proxy configuration**: Set HTTP_PROXY environment variable
|
||||
- **DNS resolution**: Try using 8.8.8.8 as DNS server
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Advanced configuration">
|
||||
```javascript
|
||||
const config = {
|
||||
performance: { cache: true, timeout: 30000 },
|
||||
security: { encryption: 'AES-256' }
|
||||
};
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### API documentation components
|
||||
|
||||
#### Parameter fields
|
||||
|
||||
<ParamField path="user_id" type="string" required>
|
||||
Unique identifier for the user. Must be a valid UUID v4 format.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="email" type="string" required>
|
||||
User's email address. Must be valid and unique within the system.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="limit" type="integer" default="10">
|
||||
Maximum number of results to return. Range: 1-100.
|
||||
</ParamField>
|
||||
|
||||
<ParamField header="Authorization" type="string" required>
|
||||
Bearer token for API authentication. Format: `Bearer YOUR_API_KEY`
|
||||
</ParamField>
|
||||
|
||||
#### Response fields
|
||||
|
||||
<ResponseField name="user_id" type="string" required>
|
||||
Unique identifier assigned to the newly created user.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="created_at" type="timestamp">
|
||||
ISO 8601 formatted timestamp of when the user was created.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="permissions" type="array">
|
||||
List of permission strings assigned to this user.
|
||||
</ResponseField>
|
||||
|
||||
#### Expandable nested fields
|
||||
|
||||
<ResponseField name="user" type="object">
|
||||
Complete user object with all associated data.
|
||||
|
||||
<Expandable title="User properties">
|
||||
<ResponseField name="profile" type="object">
|
||||
User profile information including personal details.
|
||||
|
||||
<Expandable title="Profile details">
|
||||
<ResponseField name="first_name" type="string">
|
||||
User's first name as entered during registration.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="avatar_url" type="string | null">
|
||||
URL to user's profile picture. Returns null if no avatar is set.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
### Interactive components
|
||||
|
||||
#### Cards for navigation
|
||||
|
||||
<Card title="Getting started guide" icon="rocket" href="/quickstart">
|
||||
Complete walkthrough from installation to your first API call in under 10 minutes.
|
||||
</Card>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Authentication" icon="key" href="/auth">
|
||||
Learn how to authenticate requests using API keys or JWT tokens.
|
||||
</Card>
|
||||
|
||||
<Card title="Rate limiting" icon="clock" href="/rate-limits">
|
||||
Understand rate limits and best practices for high-volume usage.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Media and advanced components
|
||||
|
||||
#### Frames for images
|
||||
|
||||
Wrap all images in frames.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/dashboard.png" alt="Main dashboard showing analytics overview" />
|
||||
</Frame>
|
||||
|
||||
<Frame caption="The analytics dashboard provides real-time insights">
|
||||
<img src="/images/analytics.png" alt="Analytics dashboard with charts" />
|
||||
</Frame>
|
||||
|
||||
#### Tooltips and updates
|
||||
|
||||
<Tooltip tip="Application Programming Interface - protocols for building software">
|
||||
API
|
||||
</Tooltip>
|
||||
|
||||
<Update label="Version 2.1.0" description="Released March 15, 2024">
|
||||
## New features
|
||||
- Added bulk user import functionality
|
||||
- Improved error messages with actionable suggestions
|
||||
|
||||
## Bug fixes
|
||||
- Fixed pagination issue with large datasets
|
||||
- Resolved authentication timeout problems
|
||||
</Update>
|
||||
|
||||
## Required page structure
|
||||
|
||||
Every documentation page must begin with YAML frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "Clear, specific, keyword-rich title"
|
||||
description: "Concise description explaining page purpose and value"
|
||||
---
|
||||
```
|
||||
|
||||
## Content quality standards
|
||||
|
||||
### Code examples requirements
|
||||
- Always include complete, runnable examples that users can copy and execute
|
||||
- Show proper error handling and edge case management
|
||||
- Use realistic data instead of placeholder values
|
||||
- Include expected outputs and results for verification
|
||||
- Test all code examples thoroughly before publishing
|
||||
- Specify language and include filename when relevant
|
||||
- Add explanatory comments for complex logic
|
||||
|
||||
### API documentation requirements
|
||||
- Document all parameters including optional ones with clear descriptions
|
||||
- Show both success and error response examples with realistic data
|
||||
- Include rate limiting information with specific limits
|
||||
- Provide authentication examples showing proper format
|
||||
- Explain all HTTP status codes and error handling
|
||||
- Cover complete request/response cycles
|
||||
|
||||
### Accessibility requirements
|
||||
- Include descriptive alt text for all images and diagrams
|
||||
- Use specific, actionable link text instead of "click here"
|
||||
- Ensure proper heading hierarchy starting with H2
|
||||
- Provide keyboard navigation considerations
|
||||
- Use sufficient color contrast in examples and visuals
|
||||
- Structure content for easy scanning with headers and lists
|
||||
|
||||
## AI assistant instructions
|
||||
|
||||
### Component selection logic
|
||||
- Use **Steps** for procedures, tutorials, setup guides, and sequential instructions
|
||||
- Use **Tabs** for platform-specific content or alternative approaches
|
||||
- Use **CodeGroup** when showing the same concept in multiple languages
|
||||
- Use **Accordions** for supplementary information that might interrupt flow
|
||||
- Use **Cards and CardGroup** for navigation, feature overviews, and related resources
|
||||
- Use **RequestExample/ResponseExample** specifically for API endpoint documentation
|
||||
- Use **ParamField** for API parameters, **ResponseField** for API responses
|
||||
- Use **Expandable** for nested object properties or hierarchical information
|
||||
|
||||
### Quality assurance checklist
|
||||
- Verify all code examples are syntactically correct and executable
|
||||
- Test all links to ensure they are functional and lead to relevant content
|
||||
- Validate Mintlify component syntax with all required properties
|
||||
- Confirm proper heading hierarchy with H2 for main sections, H3 for subsections
|
||||
- Ensure content flows logically from basic concepts to advanced topics
|
||||
- Check for consistency in terminology, formatting, and component usage
|
||||
|
||||
### Error prevention strategies
|
||||
- Always include realistic error handling in code examples
|
||||
- Provide dedicated troubleshooting sections for complex procedures
|
||||
- Explain prerequisites clearly before beginning instructions
|
||||
- Include verification and testing steps with expected outcomes
|
||||
- Add appropriate warnings for destructive or security-sensitive actions
|
||||
- Validate all technical information through testing before publication
|
||||
|
|
@ -2,6 +2,103 @@
|
|||
icon: "list-check"
|
||||
---
|
||||
|
||||
<Update label="v2.9.0" description="2024-06-23">
|
||||
|
||||
## [v2.9.0: Stuck in the Middleware With You](https://github.com/jlowin/fastmcp/releases/tag/v2.9.0)
|
||||
|
||||
FastMCP 2.9 introduces two important features that push beyond the basic MCP protocol: MCP Middleware and server-side type conversion.
|
||||
|
||||
### MCP Middleware
|
||||
MCP middleware lets you intercept and modify requests and responses at the protocol level, giving you powerful capabilities for logging, authentication, validation, and more. This is particularly useful for building production-ready MCP servers that need sophisticated request handling.
|
||||
|
||||
### Server-side Type Conversion
|
||||
This release also introduces server-side type conversion for prompt arguments, ensuring that data is properly formatted before being passed to your functions. This reduces the burden on individual tools and prompts to handle type validation and conversion.
|
||||
|
||||
## What's Changed
|
||||
### New Features 🎉
|
||||
* Add File utility for binary data by [@gorocode](https://github.com/gorocode) in [#843](https://github.com/jlowin/fastmcp/pull/843)
|
||||
* Consolidate prefix logic into FastMCP methods by [@jlowin](https://github.com/jlowin) in [#861](https://github.com/jlowin/fastmcp/pull/861)
|
||||
* Add MCP Middleware by [@jlowin](https://github.com/jlowin) in [#870](https://github.com/jlowin/fastmcp/pull/870)
|
||||
* Implement server-side type conversion for prompt arguments by [@jlowin](https://github.com/jlowin) in [#908](https://github.com/jlowin/fastmcp/pull/908)
|
||||
### Enhancements 🔧
|
||||
* Fix tool description indentation issue by [@zfflxx](https://github.com/zfflxx) in [#845](https://github.com/jlowin/fastmcp/pull/845)
|
||||
* Add version parameter to FastMCP constructor by [@mkyutani](https://github.com/mkyutani) in [#842](https://github.com/jlowin/fastmcp/pull/842)
|
||||
* Update version to not be positional by [@jlowin](https://github.com/jlowin) in [#848](https://github.com/jlowin/fastmcp/pull/848)
|
||||
* Add key to component by [@jlowin](https://github.com/jlowin) in [#869](https://github.com/jlowin/fastmcp/pull/869)
|
||||
* Add session_id property to Context for data sharing by [@jlowin](https://github.com/jlowin) in [#881](https://github.com/jlowin/fastmcp/pull/881)
|
||||
* Fix CORS documentation example by [@jlowin](https://github.com/jlowin) in [#895](https://github.com/jlowin/fastmcp/pull/895)
|
||||
### Fixes 🐞
|
||||
* "report_progress missing passing related_request_id causes notifications not working" by [@alexsee](https://github.com/alexsee) in [#838](https://github.com/jlowin/fastmcp/pull/838)
|
||||
* Fix JWT issuer validation to support string values per RFC 7519 by [@jlowin](https://github.com/jlowin) in [#892](https://github.com/jlowin/fastmcp/pull/892)
|
||||
* Fix BearerAuthProvider audience type annotations by [@jlowin](https://github.com/jlowin) in [#894](https://github.com/jlowin/fastmcp/pull/894)
|
||||
### Docs 📚
|
||||
* Add CLAUDE.md development guidelines by [@jlowin](https://github.com/jlowin) in [#880](https://github.com/jlowin/fastmcp/pull/880)
|
||||
* Update context docs for session_id property by [@jlowin](https://github.com/jlowin) in [#882](https://github.com/jlowin/fastmcp/pull/882)
|
||||
* Add API reference by [@zzstoatzz](https://github.com/zzstoatzz) in [#893](https://github.com/jlowin/fastmcp/pull/893)
|
||||
* Fix API ref rendering by [@zzstoatzz](https://github.com/zzstoatzz) in [#900](https://github.com/jlowin/fastmcp/pull/900)
|
||||
* Simplify docs nav by [@jlowin](https://github.com/jlowin) in [#902](https://github.com/jlowin/fastmcp/pull/902)
|
||||
* Add fastmcp inspect command by [@jlowin](https://github.com/jlowin) in [#904](https://github.com/jlowin/fastmcp/pull/904)
|
||||
* Update client docs by [@jlowin](https://github.com/jlowin) in [#912](https://github.com/jlowin/fastmcp/pull/912)
|
||||
* Update docs nav by [@jlowin](https://github.com/jlowin) in [#913](https://github.com/jlowin/fastmcp/pull/913)
|
||||
* Update integration documentation for Claude Desktop, ChatGPT, and Claude Code by [@jlowin](https://github.com/jlowin) in [#915](https://github.com/jlowin/fastmcp/pull/915)
|
||||
* Add http as an alias for streamable http by [@jlowin](https://github.com/jlowin) in [#917](https://github.com/jlowin/fastmcp/pull/917)
|
||||
* Clean up parameter documentation by [@jlowin](https://github.com/jlowin) in [#918](https://github.com/jlowin/fastmcp/pull/918)
|
||||
* Add middleware examples for timing, logging, rate limiting, and error handling by [@jlowin](https://github.com/jlowin) in [#919](https://github.com/jlowin/fastmcp/pull/919)
|
||||
* ControlFlow → FastMCP rename by [@jlowin](https://github.com/jlowin) in [#922](https://github.com/jlowin/fastmcp/pull/922)
|
||||
### Examples & Contrib 💡
|
||||
* Add contrib.mcp_mixin support for annotations by [@rsp2k](https://github.com/rsp2k) in [#860](https://github.com/jlowin/fastmcp/pull/860)
|
||||
* Add ATProto (Bluesky) MCP Server Example by [@zzstoatzz](https://github.com/zzstoatzz) in [#916](https://github.com/jlowin/fastmcp/pull/916)
|
||||
* Fix path in atproto example pyproject by [@zzstoatzz](https://github.com/zzstoatzz) in [#920](https://github.com/jlowin/fastmcp/pull/920)
|
||||
* Remove uv source in example by [@zzstoatzz](https://github.com/zzstoatzz) in [#921](https://github.com/jlowin/fastmcp/pull/921)
|
||||
|
||||
## New Contributors
|
||||
* [@alexsee](https://github.com/alexsee) made their first contribution in [#838](https://github.com/jlowin/fastmcp/pull/838)
|
||||
* [@zfflxx](https://github.com/zfflxx) made their first contribution in [#845](https://github.com/jlowin/fastmcp/pull/845)
|
||||
* [@mkyutani](https://github.com/mkyutani) made their first contribution in [#842](https://github.com/jlowin/fastmcp/pull/842)
|
||||
* [@gorocode](https://github.com/gorocode) made their first contribution in [#843](https://github.com/jlowin/fastmcp/pull/843)
|
||||
* [@rsp2k](https://github.com/rsp2k) made their first contribution in [#860](https://github.com/jlowin/fastmcp/pull/860)
|
||||
* [@owtaylor](https://github.com/owtaylor) made their first contribution in [#897](https://github.com/jlowin/fastmcp/pull/897)
|
||||
* [@Jason-CKY](https://github.com/Jason-CKY) made their first contribution in [#906](https://github.com/jlowin/fastmcp/pull/906)
|
||||
|
||||
**Full Changelog**: [v2.8.1...v2.9.0](https://github.com/jlowin/fastmcp/compare/v2.8.1...v2.9.0)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="v2.8.1" description="2024-06-15">
|
||||
|
||||
## [v2.8.1: Sound Judgement](https://github.com/jlowin/fastmcp/releases/tag/v2.8.1)
|
||||
|
||||
2.8.1 introduces audio support, as well as minor fixes and updates for deprecated features.
|
||||
|
||||
### Audio Support
|
||||
This release adds support for audio content in MCP tools and resources, expanding FastMCP's multimedia capabilities beyond text and images.
|
||||
|
||||
## What's Changed
|
||||
### New Features 🎉
|
||||
* Add audio support by [@jlowin](https://github.com/jlowin) in [#833](https://github.com/jlowin/fastmcp/pull/833)
|
||||
### Enhancements 🔧
|
||||
* Add flag for disabling deprecation warnings by [@jlowin](https://github.com/jlowin) in [#802](https://github.com/jlowin/fastmcp/pull/802)
|
||||
* Add examples to Tool Arg Param transformation by [@strawgate](https://github.com/strawgate) in [#806](https://github.com/jlowin/fastmcp/pull/806)
|
||||
### Fixes 🐞
|
||||
* Restore .settings access as deprecated by [@jlowin](https://github.com/jlowin) in [#800](https://github.com/jlowin/fastmcp/pull/800)
|
||||
* Ensure handling of false http kwargs correctly; removed unused kwarg by [@jlowin](https://github.com/jlowin) in [#804](https://github.com/jlowin/fastmcp/pull/804)
|
||||
* Bump mcp 1.9.4 by [@jlowin](https://github.com/jlowin) in [#835](https://github.com/jlowin/fastmcp/pull/835)
|
||||
### Docs 📚
|
||||
* Update changelog for 2.8.0 by [@jlowin](https://github.com/jlowin) in [#794](https://github.com/jlowin/fastmcp/pull/794)
|
||||
* Update welcome docs by [@jlowin](https://github.com/jlowin) in [#808](https://github.com/jlowin/fastmcp/pull/808)
|
||||
* Update headers in docs by [@jlowin](https://github.com/jlowin) in [#809](https://github.com/jlowin/fastmcp/pull/809)
|
||||
* Add MCP group to tutorials by [@jlowin](https://github.com/jlowin) in [#810](https://github.com/jlowin/fastmcp/pull/810)
|
||||
* Add Community section to documentation by [@zzstoatzz](https://github.com/zzstoatzz) in [#819](https://github.com/jlowin/fastmcp/pull/819)
|
||||
* Add 2.8 update by [@jlowin](https://github.com/jlowin) in [#821](https://github.com/jlowin/fastmcp/pull/821)
|
||||
* Embed YouTube videos in community showcase by [@zzstoatzz](https://github.com/zzstoatzz) in [#820](https://github.com/jlowin/fastmcp/pull/820)
|
||||
### Other Changes 🦾
|
||||
* Ensure http args are passed through by [@jlowin](https://github.com/jlowin) in [#803](https://github.com/jlowin/fastmcp/pull/803)
|
||||
* Fix install link in readme by [@jlowin](https://github.com/jlowin) in [#836](https://github.com/jlowin/fastmcp/pull/836)
|
||||
|
||||
**Full Changelog**: [v2.8.0...v2.8.1](https://github.com/jlowin/fastmcp/compare/v2.8.0...v2.8.1)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="v2.8.0" description="2024-06-10">
|
||||
|
||||
## [v2.8.0: Transform and Roll Out](https://github.com/jlowin/fastmcp/releases/tag/v2.8.0)
|
||||
|
|
|
|||
|
|
@ -102,14 +102,14 @@ config = {
|
|||
"mcpServers": {
|
||||
"server_name": {
|
||||
# Remote HTTP/SSE server
|
||||
"transport": "streamable-http", # or "sse"
|
||||
"transport": "http", # or "sse"
|
||||
"url": "https://api.example.com/mcp",
|
||||
"headers": {"Authorization": "Bearer token"},
|
||||
"auth": "oauth" # or bearer token string
|
||||
},
|
||||
"local_server": {
|
||||
# Local stdio server
|
||||
"transport": "stdio"
|
||||
"transport": "stdio",
|
||||
"command": "python",
|
||||
"args": ["./server.py", "--verbose"],
|
||||
"env": {"DEBUG": "true"},
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
MCP servers can emit log messages to clients. The client can handle these logs through a log handler callback.
|
||||
|
||||
## Setting Up Log Handling
|
||||
## Log Handler
|
||||
|
||||
Provide a `log_handler` function when creating the client:
|
||||
|
||||
|
|
@ -31,13 +31,27 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
## LogMessage Structure
|
||||
### Handler Parameters
|
||||
|
||||
The `log_handler` receives a `LogMessage` object with:
|
||||
The `log_handler` is called every time a log message is received. It receives a `LogMessage` object:
|
||||
|
||||
- **`level`**: Log level (e.g., "debug", "info", "warning", "error")
|
||||
- **`logger`**: Logger name (optional, may be None)
|
||||
- **`data`**: The actual log message content
|
||||
<Card icon="code" title="Log Handler Parameters">
|
||||
<ResponseField name="LogMessage" type="Log Message Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="level" type='Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]'>
|
||||
The log level
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="logger" type="str | None">
|
||||
The logger name (optional, may be None)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="data" type="Any">
|
||||
The actual log message content
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
```python
|
||||
async def detailed_log_handler(message: LogMessage):
|
||||
|
|
@ -51,13 +65,12 @@ async def detailed_log_handler(message: LogMessage):
|
|||
|
||||
## Default Log Handling
|
||||
|
||||
If you don't provide a custom `log_handler`, FastMCP uses a default handler that emits DEBUG level logs:
|
||||
If you don't provide a custom `log_handler`, FastMCP uses a default handler that emits a DEBUG-level FastMCP log for every log message received from the server, which is useful for visibility without polluting your own logs.
|
||||
|
||||
```python
|
||||
# Without custom handler - uses default DEBUG logging
|
||||
client = Client("my_mcp_server.py")
|
||||
|
||||
async with client:
|
||||
# Server logs will be emitted at DEBUG level
|
||||
# Server logs will be emitted at DEBUG level automatically
|
||||
await client.call_tool("some_tool")
|
||||
```
|
||||
129
docs/clients/messages.mdx
Normal file
129
docs/clients/messages.mdx
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
---
|
||||
title: Message Handling
|
||||
sidebarTitle: Messages
|
||||
description: Handle MCP messages, requests, and notifications with custom message handlers.
|
||||
icon: envelope
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
MCP clients can receive various types of messages from servers, including requests that need responses and notifications that don't. The message handler provides a unified way to process all these messages.
|
||||
|
||||
## Function-Based Handler
|
||||
|
||||
The simplest way to handle messages is with a function that receives all messages:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
async def message_handler(message):
|
||||
"""Handle all MCP messages from the server."""
|
||||
if hasattr(message, 'root'):
|
||||
method = message.root.method
|
||||
print(f"Received: {method}")
|
||||
|
||||
# Handle specific notifications
|
||||
if method == "notifications/tools/list_changed":
|
||||
print("Tools have changed - might want to refresh tool cache")
|
||||
elif method == "notifications/resources/list_changed":
|
||||
print("Resources have changed")
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
message_handler=message_handler,
|
||||
)
|
||||
```
|
||||
|
||||
## Message Handler Class
|
||||
|
||||
For fine-grained targeting, FastMCP provides a `MessageHandler` class you can subclass to take advantage of specific hooks:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.messages import MessageHandler
|
||||
import mcp.types
|
||||
|
||||
class MyMessageHandler(MessageHandler):
|
||||
async def on_tool_list_changed(
|
||||
self, notification: mcp.types.ToolListChangedNotification
|
||||
) -> None:
|
||||
"""Handle tool list changes specifically."""
|
||||
print("Tool list changed - refreshing available tools")
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
message_handler=MyMessageHandler(),
|
||||
)
|
||||
```
|
||||
|
||||
### Available Handler Methods
|
||||
|
||||
All handler methods receive a single argument - the specific message type:
|
||||
|
||||
<Card icon="code" title="Message Handler Methods">
|
||||
<ResponseField name="on_message(message)" type="Any MCP message">
|
||||
Called for ALL messages (requests and notifications)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_request(request)" type="mcp.types.ClientRequest">
|
||||
Called for requests that expect responses
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_notification(notification)" type="mcp.types.ServerNotification">
|
||||
Called for notifications (fire-and-forget)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_tool_list_changed(notification)" type="mcp.types.ToolListChangedNotification">
|
||||
Called when the server's tool list changes
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_resource_list_changed(notification)" type="mcp.types.ResourceListChangedNotification">
|
||||
Called when the server's resource list changes
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_prompt_list_changed(notification)" type="mcp.types.PromptListChangedNotification">
|
||||
Called when the server's prompt list changes
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_progress(notification)" type="mcp.types.ProgressNotification">
|
||||
Called for progress updates during long-running operations
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_logging_message(notification)" type="mcp.types.LoggingMessageNotification">
|
||||
Called for log messages from the server
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Example: Handling Tool Changes
|
||||
|
||||
Here's a practical example of handling tool list changes:
|
||||
|
||||
```python
|
||||
from fastmcp.client.messages import MessageHandler
|
||||
import mcp.types
|
||||
|
||||
class ToolCacheHandler(MessageHandler):
|
||||
def __init__(self):
|
||||
self.cached_tools = []
|
||||
|
||||
async def on_tool_list_changed(
|
||||
self, notification: mcp.types.ToolListChangedNotification
|
||||
) -> None:
|
||||
"""Clear tool cache when tools change."""
|
||||
print("Tools changed - clearing cache")
|
||||
self.cached_tools = [] # Force refresh on next access
|
||||
|
||||
client = Client("server.py", message_handler=ToolCacheHandler())
|
||||
```
|
||||
|
||||
## Handling Requests
|
||||
|
||||
While the message handler receives server-initiated requests, for most use cases you should use the dedicated callback parameters instead:
|
||||
|
||||
- **Sampling requests**: Use [`sampling_handler`](/clients/sampling)
|
||||
- **Progress requests**: Use [`progress_handler`](/clients/progress)
|
||||
- **Log requests**: Use [`log_handler`](/clients/logging)
|
||||
|
||||
The message handler is primarily for monitoring and handling notifications rather than responding to requests.
|
||||
|
|
@ -11,7 +11,7 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
MCP servers can report progress during long-running operations. The client can receive these updates through a progress handler.
|
||||
|
||||
## Setting Up Progress Handling
|
||||
## Progress Handler
|
||||
|
||||
Set a progress handler when creating the client:
|
||||
|
||||
|
|
@ -35,6 +35,26 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
### Handler Parameters
|
||||
|
||||
The progress handler receives three parameters:
|
||||
|
||||
|
||||
<Card icon="code" title="Progress Handler Parameters">
|
||||
<ResponseField name="progress" type="float">
|
||||
Current progress value
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="total" type="float | None">
|
||||
Expected total value (may be None)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="message" type="str | None">
|
||||
Optional status message (may be None)
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
|
||||
## Per-Call Progress Handler
|
||||
|
||||
Override the progress handler for specific tool calls:
|
||||
|
|
@ -48,12 +68,3 @@ async with client:
|
|||
progress_handler=my_progress_handler
|
||||
)
|
||||
```
|
||||
|
||||
## Handler Parameters
|
||||
|
||||
The progress handler receives:
|
||||
|
||||
- **`progress`** (float): Current progress value
|
||||
- **`total`** (float | None): Expected total value (may be None)
|
||||
- **`message`** (str | None): Optional status message (may be None)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ description: Handle server-initiated LLM sampling requests.
|
|||
icon: robot
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
MCP servers can request LLM completions from clients. The client handles these requests through a sampling handler callback.
|
||||
|
||||
## Setting Up Sampling Handling
|
||||
## Sampling Handler
|
||||
|
||||
Provide a `sampling_handler` function when creating the client:
|
||||
|
||||
|
|
@ -38,26 +38,88 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
## Handler Parameters
|
||||
### Handler Parameters
|
||||
|
||||
The sampling handler receives three parameters:
|
||||
|
||||
### SamplingMessage
|
||||
<Card icon="code" title="Sampling Handler Parameters">
|
||||
<ResponseField name="SamplingMessage" type="Sampling Message Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="role" type='Literal["user", "assistant"]'>
|
||||
The role of the message.
|
||||
</ResponseField>
|
||||
|
||||
- **`role`**: Message role (e.g., "user", "assistant", "system")
|
||||
- **`content`**: Message content (usually has `.text` attribute)
|
||||
<ResponseField name="content" type="TextContent | ImageContent | AudioContent">
|
||||
The content of the message.
|
||||
|
||||
### SamplingParams
|
||||
TextContent is most common, and has a `.text` attribute.
|
||||
</ResponseField>
|
||||
|
||||
- **`systemPrompt`**: System prompt string (optional)
|
||||
- **`maxTokens`**: Maximum tokens to generate (optional)
|
||||
- **`temperature`**: Sampling temperature (optional)
|
||||
- **`topP`**: Top-p sampling parameter (optional)
|
||||
- **`stopSequences`**: List of stop sequences (optional)
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
<ResponseField name="SamplingParams" type="Sampling Parameters Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="messages" type="list[SamplingMessage]">
|
||||
The messages to sample from
|
||||
</ResponseField>
|
||||
|
||||
### RequestContext
|
||||
<ResponseField name="modelPreferences" type="ModelPreferences | None">
|
||||
The server's preferences for which model to select. The client MAY ignore
|
||||
these preferences.
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="hints" type="list[ModelHint] | None">
|
||||
The hints to use for model selection.
|
||||
</ResponseField>
|
||||
|
||||
- **`request_id`**: Unique identifier for the sampling request
|
||||
<ResponseField name="costPriority" type="float | None">
|
||||
The cost priority for model selection.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="speedPriority" type="float | None">
|
||||
The speed priority for model selection.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="intelligencePriority" type="float | None">
|
||||
The intelligence priority for model selection.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="systemPrompt" type="str | None">
|
||||
An optional system prompt the server wants to use for sampling.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="includeContext" type="IncludeContext | None">
|
||||
A request to include context from one or more MCP servers (including the caller), to
|
||||
be attached to the prompt.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="temperature" type="float | None">
|
||||
The sampling temperature.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="maxTokens" type="int">
|
||||
The maximum number of tokens to sample.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="stopSequences" type="list[str] | None">
|
||||
The stop sequences to use for sampling.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="metadata" type="dict[str, Any] | None">
|
||||
Optional metadata to pass through to the LLM provider.
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
|
||||
</ResponseField>
|
||||
<ResponseField name="RequestContext" type="Request Context Object">
|
||||
<Expandable title="attributes">
|
||||
<ResponseField name="request_id" type="RequestId">
|
||||
Unique identifier for the MCP request
|
||||
</ResponseField>
|
||||
</Expandable>
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Basic Example
|
||||
|
||||
|
|
@ -75,10 +137,10 @@ async def basic_sampling_handler(
|
|||
for message in messages:
|
||||
content = message.content.text if hasattr(message.content, 'text') else str(message.content)
|
||||
conversation.append(f"{message.role}: {content}")
|
||||
|
||||
|
||||
# Use the system prompt if provided
|
||||
system_prompt = params.systemPrompt or "You are a helpful assistant."
|
||||
|
||||
|
||||
# Here you would integrate with your preferred LLM service
|
||||
# This is just a placeholder response
|
||||
return f"Response based on conversation: {' | '.join(conversation)}"
|
||||
|
|
@ -88,4 +150,3 @@ client = Client(
|
|||
sampling_handler=basic_sampling_handler
|
||||
)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -37,10 +37,13 @@ Execute a tool using `call_tool()` with the tool name and arguments:
|
|||
async with client:
|
||||
# Simple tool call
|
||||
result = await client.call_tool("add", {"a": 5, "b": 3})
|
||||
# result -> list[mcp.types.TextContent | mcp.types.ImageContent | ...]
|
||||
# result -> CallToolResult with structured and unstructured data
|
||||
|
||||
# Access the result content
|
||||
print(result[0].text) # Assuming TextContent, e.g., '8'
|
||||
# Access structured data (automatically deserialized)
|
||||
print(result.data) # 8 (int) or {"result": 8} for primitive types
|
||||
|
||||
# Access traditional content blocks
|
||||
print(result.content[0].text) # "8" (TextContent)
|
||||
```
|
||||
|
||||
### Advanced Execution Options
|
||||
|
|
@ -72,21 +75,97 @@ async with client:
|
|||
|
||||
## Handling Results
|
||||
|
||||
Tool execution returns a list of content objects. The most common types are:
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
- **`TextContent`**: Text-based results with a `.text` attribute
|
||||
- **`ImageContent`**: Image data with image-specific attributes
|
||||
- **`BlobContent`**: Binary data content
|
||||
Tool execution returns a `CallToolResult` object with both structured and traditional content. FastMCP's standout feature is the `.data` property, which doesn't just provide raw JSON but actually hydrates complete Python objects including complex types like datetimes, UUIDs, and custom classes.
|
||||
|
||||
### CallToolResult Properties
|
||||
|
||||
<Card icon="code" title="CallToolResult Properties">
|
||||
<ResponseField name=".data" type="Any">
|
||||
**FastMCP exclusive**: Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). Goes beyond JSON to provide complete object reconstruction from output schemas.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".content" type="list[mcp.types.ContentBlock]">
|
||||
Standard MCP content blocks (`TextContent`, `ImageContent`, `AudioContent`, etc.) available from all MCP servers.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".structured_content" type="dict[str, Any] | None">
|
||||
Standard MCP structured JSON data as sent by the server, available from all MCP servers that support structured outputs.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name=".is_error" type="bool">
|
||||
Boolean indicating if the tool execution failed.
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
### Structured Data Access
|
||||
|
||||
FastMCP's `.data` property provides fully hydrated Python objects, not just JSON dictionaries. This includes complex type reconstruction:
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
async with client:
|
||||
result = await client.call_tool("get_weather", {"city": "London"})
|
||||
|
||||
for content in result:
|
||||
if hasattr(content, 'text'):
|
||||
print(f"Text result: {content.text}")
|
||||
elif hasattr(content, 'data'):
|
||||
print(f"Binary data: {len(content.data)} bytes")
|
||||
# FastMCP reconstructs complete Python objects from the server's output schema
|
||||
weather = result.data # Server-defined WeatherReport object
|
||||
print(f"Temperature: {weather.temperature}°C at {weather.timestamp}")
|
||||
print(f"Station: {weather.station_id}")
|
||||
print(f"Humidity: {weather.humidity}%")
|
||||
|
||||
# The timestamp is a real datetime object, not a string!
|
||||
assert isinstance(weather.timestamp, datetime)
|
||||
assert isinstance(weather.station_id, UUID)
|
||||
|
||||
# Compare with raw structured JSON (standard MCP)
|
||||
print(f"Raw JSON: {result.structured_content}")
|
||||
# {"temperature": 20, "timestamp": "2024-01-15T14:30:00Z", "station_id": "123e4567-..."}
|
||||
|
||||
# Traditional content blocks (standard MCP)
|
||||
print(f"Text content: {result.content[0].text}")
|
||||
```
|
||||
|
||||
### Fallback Behavior
|
||||
|
||||
For tools without output schemas or when deserialization fails, `.data` will be `None`:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool("legacy_tool", {"param": "value"})
|
||||
|
||||
if result.data is not None:
|
||||
# Structured output available and successfully deserialized
|
||||
print(f"Structured: {result.data}")
|
||||
else:
|
||||
# No structured output or deserialization failed - use content blocks
|
||||
for content in result.content:
|
||||
if hasattr(content, 'text'):
|
||||
print(f"Text result: {content.text}")
|
||||
elif hasattr(content, 'data'):
|
||||
print(f"Binary data: {len(content.data)} bytes")
|
||||
```
|
||||
|
||||
### Primitive Type Unwrapping
|
||||
|
||||
<Tip>
|
||||
FastMCP servers automatically wrap non-object results (like `int`, `str`, `bool`) in a `{"result": value}` structure to create valid structured outputs. FastMCP clients understand this convention and automatically unwrap the value in `.data` for convenience, so you get the original primitive value instead of a wrapper object.
|
||||
</Tip>
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool("calculate_sum", {"a": 5, "b": 3})
|
||||
|
||||
# FastMCP client automatically unwraps for convenience
|
||||
print(result.data) # 8 (int) - the original value
|
||||
|
||||
# Raw structured content shows the server-side wrapping
|
||||
print(result.structured_content) # {"result": 8}
|
||||
|
||||
# Other MCP clients would need to manually access ["result"]
|
||||
# value = result.structured_content["result"] # Not needed with FastMCP!
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
|
@ -101,14 +180,32 @@ from fastmcp.exceptions import ToolError
|
|||
async with client:
|
||||
try:
|
||||
result = await client.call_tool("potentially_failing_tool", {"param": "value"})
|
||||
print("Tool succeeded:", result)
|
||||
print("Tool succeeded:", result.data)
|
||||
except ToolError as e:
|
||||
print(f"Tool failed: {e}")
|
||||
```
|
||||
|
||||
### Manual Error Checking
|
||||
|
||||
For more granular control, use `call_tool_mcp()` which returns the raw MCP protocol object with an `isError` flag:
|
||||
You can disable automatic error raising and manually check the result:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool(
|
||||
"potentially_failing_tool",
|
||||
{"param": "value"},
|
||||
raise_on_error=False
|
||||
)
|
||||
|
||||
if result.is_error:
|
||||
print(f"Tool failed: {result.content[0].text}")
|
||||
else:
|
||||
print(f"Tool succeeded: {result.data}")
|
||||
```
|
||||
|
||||
### Raw MCP Protocol Access
|
||||
|
||||
For complete control, use `call_tool_mcp()` which returns the raw MCP protocol object:
|
||||
|
||||
```python
|
||||
async with client:
|
||||
|
|
@ -119,6 +216,7 @@ async with client:
|
|||
print(f"Tool failed: {result.content}")
|
||||
else:
|
||||
print(f"Tool succeeded: {result.content}")
|
||||
# Note: No automatic deserialization with call_tool_mcp()
|
||||
```
|
||||
|
||||
## Argument Handling
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ Streamable HTTP is the recommended transport for web-based deployments, providin
|
|||
|
||||
- **Class:** `fastmcp.client.transports.StreamableHttpTransport`
|
||||
- **Inferred From:** URLs starting with `http://` or `https://` (default for HTTP URLs since v2.3.0) that do not contain `/sse/` in the path
|
||||
- **Server Compatibility:** Works with FastMCP servers running in `streamable-http` mode
|
||||
- **Server Compatibility:** Works with FastMCP servers running in `http` mode
|
||||
|
||||
#### Basic Usage
|
||||
|
||||
|
|
@ -150,7 +150,7 @@ client = Client(transport)
|
|||
- **Use Streamable HTTP when:**
|
||||
- Setting up new deployments (recommended default)
|
||||
- You need bidirectional streaming
|
||||
- You're connecting to FastMCP servers running in `streamable-http` mode
|
||||
- You're connecting to FastMCP servers running in `http` mode
|
||||
|
||||
- **Use SSE when:**
|
||||
- Connecting to legacy FastMCP servers running in `sse` mode
|
||||
|
|
@ -397,7 +397,7 @@ config = {
|
|||
# Remote HTTP server
|
||||
"weather": {
|
||||
"url": "https://weather-api.example.com/mcp",
|
||||
"transport": "streamable-http"
|
||||
"transport": "http"
|
||||
},
|
||||
# Local stdio server
|
||||
"assistant": {
|
||||
|
|
@ -408,7 +408,7 @@ config = {
|
|||
# Another remote server
|
||||
"calendar": {
|
||||
"url": "https://calendar-api.example.com/mcp",
|
||||
"transport": "streamable-http"
|
||||
"transport": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,3 @@
|
|||
/* Code highlighting -- target only inline code elements, not code blocks */
|
||||
p code:not(pre code),
|
||||
table code:not(pre code),
|
||||
li code:not(pre code),
|
||||
h1 code:not(pre code),
|
||||
h2 code:not(pre code),
|
||||
h3 code:not(pre code),
|
||||
h4 code:not(pre code),
|
||||
h5 code:not(pre code),
|
||||
h6 code:not(pre code) {
|
||||
color: #f72585 !important;
|
||||
background-color: rgba(247, 37, 133, 0.09);
|
||||
}
|
||||
|
||||
/* Banner styling -- improve readability with better contrast */
|
||||
#banner {
|
||||
background: #f1f5f9 !important;
|
||||
|
|
@ -79,41 +65,3 @@ h6 code:not(pre code) {
|
|||
color: #f1f5f9 !important;
|
||||
}
|
||||
|
||||
/* Version badge -- display a badge with the current version of the documentation */
|
||||
.version-badge {
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
gap: 0.3em;
|
||||
font-size: 1em;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
font-family: "Inter", sans-serif;
|
||||
color: #ff5400;
|
||||
background: #fef2f2;
|
||||
border: 1px solid rgba(220, 38, 38, 0.3);
|
||||
border-radius: 12px;
|
||||
box-shadow: none;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
transition: box-shadow 0.2s, transform 0.15s;
|
||||
}
|
||||
|
||||
.version-badge-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.version-badge:hover {
|
||||
box-shadow: 0 2px 8px 0 rgba(160, 132, 252, 0.1);
|
||||
transform: translateY(-1px) scale(1.03);
|
||||
}
|
||||
|
||||
.dark .version-badge {
|
||||
color: #f1f5f9;
|
||||
background: #334155;
|
||||
border: 1px solid #64748b;
|
||||
}
|
||||
3
docs/css/python-sdk.css
Normal file
3
docs/css/python-sdk.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
a:has(svg.icon) {
|
||||
border: none !important;
|
||||
}
|
||||
13
docs/css/style.css
Normal file
13
docs/css/style.css
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* Code highlighting -- target only inline code elements, not code blocks */
|
||||
p code:not(pre code),
|
||||
table code:not(pre code),
|
||||
li code:not(pre code),
|
||||
h1 code:not(pre code),
|
||||
h2 code:not(pre code),
|
||||
h3 code:not(pre code),
|
||||
h4 code:not(pre code),
|
||||
h5 code:not(pre code),
|
||||
h6 code:not(pre code) {
|
||||
color: #f72585 !important;
|
||||
background-color: rgba(247, 37, 133, 0.09);
|
||||
}
|
||||
39
docs/css/version-badge.css
Normal file
39
docs/css/version-badge.css
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* Version badge -- display a badge with the current version of the documentation */
|
||||
.version-badge {
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
gap: 0.3em;
|
||||
font-size: 1em;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
font-family: "Inter", sans-serif;
|
||||
color: #ff5400;
|
||||
background: #fef2f2;
|
||||
border: 1px solid rgba(220, 38, 38, 0.3);
|
||||
border-radius: 12px;
|
||||
box-shadow: none;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
transition: box-shadow 0.2s, transform 0.15s;
|
||||
}
|
||||
|
||||
.version-badge-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.version-badge:hover {
|
||||
box-shadow: 0 2px 8px 0 rgba(160, 132, 252, 0.1);
|
||||
transform: translateY(-1px) scale(1.03);
|
||||
}
|
||||
|
||||
.dark .version-badge {
|
||||
color: #f1f5f9;
|
||||
background: #334155;
|
||||
border: 1px solid #64748b;
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ When using Stdio transport, you will typically *not* run the server yourself as
|
|||
|
||||
Streamable HTTP is a modern, efficient transport for exposing your MCP server via HTTP. It is the recommended transport for web-based deployments.
|
||||
|
||||
To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"streamable-http"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and path (`/mcp/`).
|
||||
To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"http"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and path (`/mcp/`).
|
||||
<CodeGroup>
|
||||
```python {6} server.py
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -113,7 +113,7 @@ from fastmcp import FastMCP
|
|||
mcp = FastMCP()
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
mcp.run(transport="http")
|
||||
```
|
||||
```python {5} client.py
|
||||
import asyncio
|
||||
|
|
@ -128,6 +128,10 @@ if __name__ == "__main__":
|
|||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Tip>
|
||||
For backward compatibility, wherever `"http"` is accepted as a transport name, you can also pass `"streamable-http"` as a fully supported alias. This is particularly useful when upgrading from FastMCP 1.x in the official Python SDK and FastMCP \<= 2.9, where `"streamable-http"` was the standard name.
|
||||
</Tip>
|
||||
|
||||
To customize the host, port, path, or log level, provide appropriate keyword arguments to the `run()` method.
|
||||
|
||||
<CodeGroup>
|
||||
|
|
@ -138,7 +142,7 @@ mcp = FastMCP()
|
|||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(
|
||||
transport="streamable-http",
|
||||
transport="http",
|
||||
host="127.0.0.1",
|
||||
port=4200,
|
||||
path="/my-custom-path",
|
||||
|
|
@ -158,7 +162,6 @@ if __name__ == "__main__":
|
|||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
### SSE
|
||||
|
||||
<Warning>
|
||||
|
|
@ -250,7 +253,7 @@ def hello(name: str) -> str:
|
|||
|
||||
async def main():
|
||||
# Use run_async() in async contexts
|
||||
await mcp.run_async(transport="streamable-http")
|
||||
await mcp.run_async(transport="http")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@
|
|||
"clients/logging",
|
||||
"clients/progress",
|
||||
"clients/sampling",
|
||||
"clients/messages",
|
||||
"clients/roots"
|
||||
]
|
||||
},
|
||||
|
|
@ -124,9 +125,11 @@
|
|||
"group": "Integrations",
|
||||
"pages": [
|
||||
"integrations/anthropic",
|
||||
"integrations/chatgpt",
|
||||
"integrations/claude-code",
|
||||
"integrations/claude-desktop",
|
||||
"integrations/openai",
|
||||
"integrations/gemini",
|
||||
"integrations/openai",
|
||||
"integrations/contrib"
|
||||
]
|
||||
},
|
||||
|
|
@ -155,7 +158,6 @@
|
|||
"anchor": "What's New",
|
||||
"pages": ["updates", "changelog"]
|
||||
},
|
||||
|
||||
{
|
||||
"anchor": "Community",
|
||||
"icon": "users",
|
||||
|
|
@ -243,7 +245,17 @@
|
|||
"python-sdk/fastmcp-server-context",
|
||||
"python-sdk/fastmcp-server-dependencies",
|
||||
"python-sdk/fastmcp-server-http",
|
||||
"python-sdk/fastmcp-server-middleware",
|
||||
{
|
||||
"group": "middleware",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-server-middleware-__init__",
|
||||
"python-sdk/fastmcp-server-middleware-error_handling",
|
||||
"python-sdk/fastmcp-server-middleware-logging",
|
||||
"python-sdk/fastmcp-server-middleware-middleware",
|
||||
"python-sdk/fastmcp-server-middleware-rate_limiting",
|
||||
"python-sdk/fastmcp-server-middleware-timing"
|
||||
]
|
||||
},
|
||||
"python-sdk/fastmcp-server-openapi",
|
||||
"python-sdk/fastmcp-server-proxy",
|
||||
"python-sdk/fastmcp-server-server"
|
||||
|
|
@ -266,10 +278,12 @@
|
|||
"python-sdk/fastmcp-utilities-components",
|
||||
"python-sdk/fastmcp-utilities-exceptions",
|
||||
"python-sdk/fastmcp-utilities-http",
|
||||
"python-sdk/fastmcp-utilities-inspect",
|
||||
"python-sdk/fastmcp-utilities-json_schema",
|
||||
"python-sdk/fastmcp-utilities-logging",
|
||||
"python-sdk/fastmcp-utilities-mcp_config",
|
||||
"python-sdk/fastmcp-utilities-openapi",
|
||||
"python-sdk/fastmcp-utilities-tests",
|
||||
"python-sdk/fastmcp-utilities-types"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ FastMCP root path: ~/Developer/fastmcp
|
|||
Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient.
|
||||
|
||||
|
||||
```python {1-5}
|
||||
```python {5}
|
||||
# Before
|
||||
# from mcp.server.fastmcp import FastMCP
|
||||
|
||||
|
|
@ -56,8 +56,9 @@ from fastmcp import FastMCP
|
|||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities.
|
||||
Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities.
|
||||
</Warning>
|
||||
|
||||
## Versioning and Breaking Changes
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ def roll_dice(n_dice: int) -> list[int]:
|
|||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="sse", port=8000)
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
## Deploy the Server
|
||||
|
|
@ -70,7 +70,7 @@ You'll also need to authenticate with Anthropic. You can do this by setting the
|
|||
export ANTHROPIC_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/sse` as the endpoint because we deployed an SSE server with the default path; you may need to use a different endpoint if you customized your server's deployment. **At this time you must also include the `extra_headers` parameter with the `anthropic-beta` header.**
|
||||
Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/mcp/` as the endpoint because we deployed a streamable-HTTP server with the default path; you may need to use a different endpoint if you customized your server's deployment. **At this time you must also include the `extra_headers` parameter with the `anthropic-beta` header.**
|
||||
|
||||
```python {5, 13-22}
|
||||
import anthropic
|
||||
|
|
@ -88,7 +88,7 @@ response = client.beta.messages.create(
|
|||
mcp_servers=[
|
||||
{
|
||||
"type": "url",
|
||||
"url": f"{url}/sse",
|
||||
"url": f"{url}/mcp/",
|
||||
"name": "dice-server",
|
||||
}
|
||||
],
|
||||
|
|
@ -175,7 +175,7 @@ def roll_dice(n_dice: int) -> list[int]:
|
|||
|
||||
if __name__ == "__main__":
|
||||
print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
|
||||
mcp.run(transport="sse", port=8000)
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
### Client Authentication
|
||||
|
|
@ -213,7 +213,7 @@ response = client.beta.messages.create(
|
|||
mcp_servers=[
|
||||
{
|
||||
"type": "url",
|
||||
"url": f"{url}/sse",
|
||||
"url": f"{url}/mcp/",
|
||||
"name": "dice-server",
|
||||
"authorization_token": access_token
|
||||
}
|
||||
|
|
|
|||
158
docs/integrations/chatgpt.mdx
Normal file
158
docs/integrations/chatgpt.mdx
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
---
|
||||
title: ChatGPT + FastMCP
|
||||
sidebarTitle: ChatGPT
|
||||
description: Connect FastMCP servers to ChatGPT Deep Research
|
||||
icon: message-smile
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
ChatGPT supports MCP servers through remote HTTP connections, allowing you to extend ChatGPT's capabilities with custom tools and knowledge from your FastMCP servers.
|
||||
|
||||
<Note>
|
||||
MCP integration with ChatGPT is currently limited to **Deep Research** functionality and is not available for general chat. This feature is available for ChatGPT Pro, Team, Enterprise, and Edu users.
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
OpenAI's official MCP documentation and examples are built with **FastMCP v2**! Check out their [sample MCP server](https://github.com/openai/mcp-server-sample) which demonstrates FastMCP in action.
|
||||
</Tip>
|
||||
|
||||
## Deep Research
|
||||
|
||||
ChatGPT's Deep Research feature requires MCP servers to be internet-accessible HTTP endpoints with **exactly two specific tools**:
|
||||
|
||||
- **`search`**: For searching through your resources and returning matching IDs
|
||||
- **`fetch`**: For retrieving the full content of specific resources by ID
|
||||
|
||||
<Warning>
|
||||
If your server doesn't implement both `search` and `fetch` tools with the correct signatures, ChatGPT will show the error: "This MCP server doesn't implement our specification". Both tools are required.
|
||||
</Warning>
|
||||
|
||||
### Tool Descriptions Matter
|
||||
|
||||
Since ChatGPT needs to understand how to use your tools effectively, **write detailed tool descriptions**. The description teaches ChatGPT how to form queries, what parameters to use, and what to expect from your data. Poor descriptions lead to poor search results.
|
||||
|
||||
### Create a Server
|
||||
|
||||
A Deep Research-compatible server must implement these two required tools:
|
||||
|
||||
- **`search(query: str)`** - Takes a query of any kind and returns matching record IDs
|
||||
- **`fetch(id: str)`** - Takes an ID and returns the record
|
||||
|
||||
**Critical**: Write detailed docstrings for both tools. These descriptions teach ChatGPT how to use your tools effectively. Poor descriptions lead to poor search results.
|
||||
|
||||
The `search` tool should take a query (of any kind!) and return IDs. The `fetch` tool should take an ID and return the record.
|
||||
|
||||
Here's a reference server implementation you can adapt (see also [OpenAI's sample server](https://github.com/openai/mcp-server-sample) for comparison):
|
||||
|
||||
```python server.py [expandable]
|
||||
import json
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from fastmcp import FastMCP
|
||||
|
||||
@dataclass
|
||||
class Record:
|
||||
id: str
|
||||
title: str
|
||||
text: str
|
||||
metadata: dict
|
||||
|
||||
def create_server(
|
||||
records_path: Path | str,
|
||||
name: str | None = None,
|
||||
instructions: str | None = None,
|
||||
) -> FastMCP:
|
||||
"""Create a FastMCP server that can search and fetch records from a JSON file."""
|
||||
records = json.loads(Path(records_path).read_text())
|
||||
|
||||
RECORDS = [Record(**r) for r in records]
|
||||
LOOKUP = {r.id: r for r in RECORDS}
|
||||
|
||||
mcp = FastMCP(name=name or "Deep Research MCP", instructions=instructions)
|
||||
|
||||
@mcp.tool()
|
||||
async def search(query: str):
|
||||
"""
|
||||
Simple unranked keyword search across title, text, and metadata.
|
||||
Searches for any of the query terms in the record content.
|
||||
Returns a list of matching record IDs for ChatGPT to fetch.
|
||||
"""
|
||||
toks = query.lower().split()
|
||||
ids = []
|
||||
for r in RECORDS:
|
||||
record_txt = " ".join(
|
||||
[r.title, r.text, " ".join(r.metadata.values())]
|
||||
).lower()
|
||||
if any(t in record_txt for t in toks):
|
||||
ids.append(r.id)
|
||||
|
||||
return {"ids": ids}
|
||||
|
||||
@mcp.tool()
|
||||
async def fetch(id: str):
|
||||
"""
|
||||
Fetch a record by ID.
|
||||
Returns the complete record data for ChatGPT to analyze and cite.
|
||||
"""
|
||||
if id not in LOOKUP:
|
||||
raise ValueError(f"Unknown record ID: {id}")
|
||||
return LOOKUP[id]
|
||||
|
||||
return mcp
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp = create_server("path/to/records.json")
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
### Deploy the Server
|
||||
|
||||
Your server must be deployed to a public URL in order for ChatGPT to access it.
|
||||
|
||||
For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server.
|
||||
|
||||
Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet:
|
||||
|
||||
<CodeGroup>
|
||||
```bash FastMCP server
|
||||
python server.py
|
||||
```
|
||||
|
||||
```bash ngrok
|
||||
ngrok http 8000
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Warning>
|
||||
This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
|
||||
</Warning>
|
||||
|
||||
### Connect to ChatGPT
|
||||
|
||||
Replace `https://your-server-url.com` with the actual URL of your server (such as your ngrok URL).
|
||||
|
||||
1. Open ChatGPT and go to **Settings** → **Connectors**
|
||||
2. Click **Add custom connector**
|
||||
3. Enter your server details:
|
||||
- **Name**: Library Catalog
|
||||
- **URL**: Your server URL (e.g., `https://abc123.ngrok.io`)
|
||||
- **Description**: A library catalog for searching and retrieving books
|
||||
|
||||
#### Test the Connection
|
||||
|
||||
1. Start a new chat in ChatGPT
|
||||
2. Click **Tools** → **Run deep research**
|
||||
3. Select your **Library Catalog** connector as a source
|
||||
4. Ask questions like:
|
||||
- "Search for Python programming books"
|
||||
- "Find books about AI and machine learning"
|
||||
- "Show me books by the Python Software Foundation"
|
||||
|
||||
ChatGPT will use your server's search and fetch tools to find relevant information and cite the sources in its response.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### "This MCP server doesn't implement our specification"
|
||||
|
||||
|
||||
If you get this error, it most likely means that your server doesn't implement the required tools (`search` and `fetch`). To correct it, ensure that your server meets the service requirements.
|
||||
60
docs/integrations/claude-code.mdx
Normal file
60
docs/integrations/claude-code.mdx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: Claude Code + FastMCP
|
||||
sidebarTitle: Claude Code
|
||||
description: Connect FastMCP servers to Claude Code
|
||||
icon: message-smile
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
Claude Code supports MCP servers through multiple transport methods, allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
||||
|
||||
<Note>
|
||||
Claude Code supports both local and remote MCP servers with flexible configuration options. See the [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp) for other transport methods.
|
||||
</Note>
|
||||
|
||||
<Tip>
|
||||
Claude Code provides built-in MCP management commands to easily add, configure, and authenticate your FastMCP servers.
|
||||
</Tip>
|
||||
|
||||
## Create a Server
|
||||
|
||||
You can create FastMCP servers using STDIO transport, remote HTTP servers, or local HTTP servers. This example shows one common approach: running an HTTP server locally for development.
|
||||
|
||||
```python server.py
|
||||
import random
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="Dice Roller")
|
||||
|
||||
@mcp.tool
|
||||
def roll_dice(n_dice: int) -> list[int]:
|
||||
"""Roll `n_dice` 6-sided dice and return the results."""
|
||||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
## Connect to Claude Code
|
||||
|
||||
Start your server and add it to Claude Code:
|
||||
|
||||
```bash
|
||||
# Start your server first
|
||||
python server.py
|
||||
```
|
||||
|
||||
Then add it to Claude Code:
|
||||
```bash
|
||||
claude mcp add dice --transport http http://localhost:8000/mcp/
|
||||
```
|
||||
|
||||
## Using Your Server
|
||||
|
||||
Once connected, Claude Code will automatically discover and use your server's tools when relevant:
|
||||
|
||||
```
|
||||
Roll some dice for me
|
||||
```
|
||||
|
||||
Claude will call your `roll_dice` tool and provide the results. If your server provides resources, you can reference them with `@` mentions like `@dice:file://path/to/resource`.
|
||||
|
|
@ -2,11 +2,15 @@
|
|||
title: Claude Desktop + FastMCP
|
||||
sidebarTitle: Claude Desktop
|
||||
description: Call FastMCP servers from Claude Desktop
|
||||
icon: desktop
|
||||
icon: message-smile
|
||||
---
|
||||
|
||||
|
||||
Claude Desktop supports MCP servers through local STDIO connections, allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
||||
Claude Desktop supports MCP servers through local STDIO connections and remote servers (beta), allowing you to extend Claude's capabilities with custom tools, resources, and prompts from your FastMCP servers.
|
||||
|
||||
<Note>
|
||||
Remote MCP server support is currently in beta and available for users on Claude Pro, Max, Team, and Enterprise plans (as of June 2025). Most users will still need to use local STDIO connections.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
This guide focuses specifically on using FastMCP servers with Claude Desktop. For general Claude Desktop MCP setup and official examples, see the [official Claude Desktop quickstart guide](https://modelcontextprotocol.io/quickstart/user).
|
||||
|
|
@ -15,10 +19,10 @@ This guide focuses specifically on using FastMCP servers with Claude Desktop. Fo
|
|||
|
||||
## Requirements
|
||||
|
||||
Claude Desktop requires MCP servers to run locally using STDIO transport. This means your server will communicate with Claude through standard input/output rather than HTTP.
|
||||
Claude Desktop traditionally requires MCP servers to run locally using STDIO transport, where your server communicates with Claude through standard input/output rather than HTTP. However, users on certain plans now have access to remote server support as well.
|
||||
|
||||
<Tip>
|
||||
If you need to connect to remote servers, you can create a **proxy server** that runs locally via STDIO and forwards requests to remote HTTP servers. See the [Proxy Servers](#proxy-servers) section below.
|
||||
If you don't have access to remote server support or need to connect to remote servers, you can create a **proxy server** that runs locally via STDIO and forwards requests to remote HTTP servers. See the [Proxy Servers](#proxy-servers) section below.
|
||||
</Tip>
|
||||
|
||||
## Create a Server
|
||||
|
|
@ -181,7 +185,7 @@ Claude Desktop runs servers in a completely isolated environment with no access
|
|||
## Remote Servers
|
||||
|
||||
|
||||
Claude Desktop only supports local STDIO servers, but FastMCP can create a proxy server that forwards requests to a remote HTTP server. You can install the proxy server in Claude Desktop.
|
||||
Users on Claude Pro, Max, Team, and Enterprise plans have first-class remote server support via integrations. For other users, or as an alternative approach, FastMCP can create a proxy server that forwards requests to a remote HTTP server. You can install the proxy server in Claude Desktop.
|
||||
|
||||
Create a proxy server that connects to a remote HTTP server:
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ from fastmcp import Client
|
|||
from fastmcp.client.auth import BearerAuth
|
||||
|
||||
mcp_client = Client(
|
||||
"https://my-server.com/sse",
|
||||
"https://my-server.com/mcp/",
|
||||
auth=BearerAuth("<your-token>"),
|
||||
)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ def roll_dice(n_dice: int) -> list[int]:
|
|||
return [random.randint(1, 6) for _ in range(n_dice)]
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="sse", port=8000)
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
### Deploy the Server
|
||||
|
|
@ -77,7 +77,7 @@ You'll also need to authenticate with OpenAI. You can do this by setting the `OP
|
|||
export OPENAI_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/sse` as the endpoint because we deployed an SSE server with the default path; you may need to use a different endpoint if you customized your server's deployment.
|
||||
Here is an example of how to call your server from Python. Note that you'll need to replace `https://your-server-url.com` with the actual URL of your server. In addition, we use `/mcp/` as the endpoint because we deployed a streamable-HTTP server with the default path; you may need to use a different endpoint if you customized your server's deployment.
|
||||
|
||||
```python {4, 11-16}
|
||||
from openai import OpenAI
|
||||
|
|
@ -93,7 +93,7 @@ resp = client.responses.create(
|
|||
{
|
||||
"type": "mcp",
|
||||
"server_label": "dice_server",
|
||||
"server_url": f"{url}/sse",
|
||||
"server_url": f"{url}/mcp/",
|
||||
"require_approval": "never",
|
||||
},
|
||||
],
|
||||
|
|
@ -172,7 +172,7 @@ def roll_dice(n_dice: int) -> list[int]:
|
|||
|
||||
if __name__ == "__main__":
|
||||
print(f"\n---\n\n🔑 Dice Roller access token:\n\n{access_token}\n\n---\n")
|
||||
mcp.run(transport="sse", port=8000)
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
#### Client Authentication
|
||||
|
|
@ -212,7 +212,7 @@ resp = client.responses.create(
|
|||
{
|
||||
"type": "mcp",
|
||||
"server_label": "dice_server",
|
||||
"server_url": f"{url}/sse",
|
||||
"server_url": f"{url}/mcp/",
|
||||
"require_approval": "never",
|
||||
"headers": {
|
||||
"Authorization": f"Bearer {access_token}"
|
||||
|
|
|
|||
|
|
@ -42,11 +42,12 @@ This command runs the server directly in your current Python environment. You ar
|
|||
|
||||
| Option | Flag | Description |
|
||||
| ------ | ---- | ----------- |
|
||||
| Transport | `--transport`, `-t` | Transport protocol to use (`stdio`, `streamable-http`, or `sse`) |
|
||||
| Transport | `--transport`, `-t` | Transport protocol to use (`stdio`, `http`, or `sse`) |
|
||||
| Host | `--host` | Host to bind to when using http transport (default: 127.0.0.1) |
|
||||
| Port | `--port`, `-p` | Port to bind to when using http transport (default: 8000) |
|
||||
| Log Level | `--log-level`, `-l` | Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
|
||||
|
||||
|
||||
#### Server Specification
|
||||
<VersionBadge version="2.3.5" />
|
||||
|
||||
|
|
@ -79,14 +80,14 @@ if __name__ == "__main__":
|
|||
You can run it with Streamable HTTP transport regardless of what's in the `__main__` block:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport streamable-http --port 8000
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
```bash
|
||||
# Run a local server with Streamable HTTP transport on a custom port
|
||||
fastmcp run server.py --transport streamable-http --port 8000
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
|
||||
# Connect to a remote server and proxy as a stdio server
|
||||
fastmcp run https://example.com/mcp-server
|
||||
|
|
@ -112,14 +113,14 @@ The `dev` command is a shortcut for testing a server over STDIO only. When the I
|
|||
1. Select "STDIO" from the transport dropdown
|
||||
2. Connect manually
|
||||
|
||||
This command does not support HTTP testing. To test a server over HTTP:
|
||||
1. Start your server manually with HTTP transport using either:
|
||||
This command does not support HTTP testing. To test a server over Streamable HTTP or SSE:
|
||||
1. Start your server manually with the appropriate transport using either the command line:
|
||||
```bash
|
||||
fastmcp run server.py --transport streamable-http
|
||||
fastmcp run server.py --transport http
|
||||
```
|
||||
or
|
||||
or by setting the transport in your code:
|
||||
```bash
|
||||
python server.py # Assuming your __main__ block sets HTTP transport
|
||||
python server.py # Assuming your __main__ block sets Streamable HTTP transport
|
||||
```
|
||||
2. Open the MCP Inspector separately and connect to your running server
|
||||
</Warning>
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ The `Tool.from_tool()` class method is the primary way to create a transformed t
|
|||
- `description`: An optional description for the new tool.
|
||||
- `transform_args`: A dictionary of `ArgTransform` objects, one for each argument you want to modify.
|
||||
- `transform_fn`: An optional function that will be called instead of the parent tool's logic.
|
||||
- `output_schema`: Control output schema and structured outputs (see [Output Schema Control](#output-schema-control)).
|
||||
- `tags`: An optional set of tags for the new tool.
|
||||
- `annotations`: An optional set of `ToolAnnotations` for the new tool.
|
||||
- `serializer`: An optional function that will be called to serialize the result of the new tool.
|
||||
|
|
@ -439,7 +440,44 @@ mcp.add_tool(new_tool)
|
|||
|
||||
<Tip>
|
||||
In the above example, `**kwargs` receives the renamed argument `b`, not the original argument `y`. It is therefore recommended to use with `forward()`, not `forward_raw()`.
|
||||
</Tip>
|
||||
</Tip>
|
||||
|
||||
## Output Schema Control
|
||||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
Transformed tools inherit output schemas from their parent by default, but you can control this behavior:
|
||||
|
||||
**Inherit from Parent (Default)**
|
||||
```python
|
||||
Tool.from_tool(parent_tool, name="renamed_tool")
|
||||
```
|
||||
The transformed tool automatically uses the parent tool's output schema and structured output behavior.
|
||||
|
||||
**Custom Output Schema**
|
||||
```python
|
||||
Tool.from_tool(parent_tool, output_schema={
|
||||
"type": "object",
|
||||
"properties": {"status": {"type": "string"}}
|
||||
})
|
||||
```
|
||||
Provide your own schema that differs from the parent. The tool must return data matching this schema.
|
||||
|
||||
**Remove Output Schema**
|
||||
```python
|
||||
Tool.from_tool(parent_tool, output_schema=False)
|
||||
```
|
||||
Removes the output schema declaration. Automatic structured content still works for object-like returns (dict, dataclass, Pydantic models) but primitive types won't be structured.
|
||||
|
||||
**Full Control with Transform Functions**
|
||||
```python
|
||||
async def custom_output(**kwargs) -> ToolResult:
|
||||
result = await forward(**kwargs)
|
||||
return ToolResult(content=[...], structured_content={...})
|
||||
|
||||
Tool.from_tool(parent_tool, transform_fn=custom_output)
|
||||
```
|
||||
Use a transform function returning `ToolResult` for complete control over both content blocks and structured outputs.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Claude app integration utilities.
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_claude_config_path`
|
||||
### `get_claude_config_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/claude.py#L14"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_claude_config_path() -> Path | None
|
||||
|
|
@ -20,7 +20,7 @@ get_claude_config_path() -> Path | None
|
|||
Get the Claude config directory based on platform.
|
||||
|
||||
|
||||
### `update_claude_config`
|
||||
### `update_claude_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/claude.py#L32"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
update_claude_config(file_spec: str, server_name: str) -> bool
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ FastMCP CLI tools.
|
|||
|
||||
## Functions
|
||||
|
||||
### `version`
|
||||
### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L87"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
version(ctx: Context)
|
||||
```
|
||||
|
||||
### `dev`
|
||||
### `dev` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L110"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
dev(server_spec: str = typer.Argument(..., help='Python file to run, optionally with :object suffix'), with_editable: Annotated[Path | None, typer.Option('--with-editable', '-e', help='Directory containing pyproject.toml to install in editable mode', exists=True, file_okay=False, resolve_path=True)] = None, with_packages: Annotated[list[str], typer.Option('--with', help='Additional packages to install')] = [], inspector_version: Annotated[str | None, typer.Option('--inspector-version', help='Version of the MCP Inspector to use')] = None, ui_port: Annotated[int | None, typer.Option('--ui-port', help='Port for the MCP Inspector UI')] = None, server_port: Annotated[int | None, typer.Option('--server-port', help='Port for the MCP Inspector Proxy server')] = None) -> None
|
||||
|
|
@ -26,10 +26,10 @@ dev(server_spec: str = typer.Argument(..., help='Python file to run, optionally
|
|||
Run a MCP server with the MCP Inspector.
|
||||
|
||||
|
||||
### `run`
|
||||
### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L227"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
run(ctx: typer.Context, server_spec: str = typer.Argument(..., help='Python file, object specification (file:obj), or URL'), transport: Annotated[str | None, typer.Option('--transport', '-t', help='Transport protocol to use (stdio, streamable-http, or sse)')] = None, host: Annotated[str | None, typer.Option('--host', help='Host to bind to when using http transport (default: 127.0.0.1)')] = None, port: Annotated[int | None, typer.Option('--port', '-p', help='Port to bind to when using http transport (default: 8000)')] = None, log_level: Annotated[str | None, typer.Option('--log-level', '-l', help='Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)')] = None) -> None
|
||||
run(ctx: typer.Context, server_spec: str = typer.Argument(..., help='Python file, object specification (file:obj), or URL'), transport: Annotated[str | None, typer.Option('--transport', '-t', help='Transport protocol to use (stdio, http, or sse)')] = None, host: Annotated[str | None, typer.Option('--host', help='Host to bind to when using http transport (default: 127.0.0.1)')] = None, port: Annotated[int | None, typer.Option('--port', '-p', help='Port to bind to when using http transport (default: 8000)')] = None, log_level: Annotated[str | None, typer.Option('--log-level', '-l', help='Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)')] = None) -> None
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ Server arguments can be passed after -- :
|
|||
fastmcp run server.py -- --config config.json --debug
|
||||
|
||||
|
||||
### `install`
|
||||
### `install` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L313"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
install(server_spec: str = typer.Argument(..., help='Python file to run, optionally with :object suffix'), server_name: Annotated[str | None, typer.Option('--name', '-n', help="Custom name for the server (defaults to server's name attribute or file name)")] = None, with_editable: Annotated[Path | None, typer.Option('--with-editable', '-e', help='Directory containing pyproject.toml to install in editable mode', exists=True, file_okay=False, resolve_path=True)] = None, with_packages: Annotated[list[str], typer.Option('--with', help='Additional packages to install')] = [], env_vars: Annotated[list[str], typer.Option('--env-var', '-v', help='Environment variables in KEY=VALUE format')] = [], env_file: Annotated[Path | None, typer.Option('--env-file', '-f', help='Load environment variables from a .env file', exists=True, file_okay=True, dir_okay=False, resolve_path=True)] = None) -> None
|
||||
|
|
@ -63,3 +63,25 @@ Install a MCP server in the Claude desktop app.
|
|||
Environment variables are preserved once added and only updated if new values
|
||||
are explicitly provided.
|
||||
|
||||
|
||||
### `inspect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L444"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
inspect(server_spec: str = typer.Argument(..., help='Python file to inspect, optionally with :object suffix'), output: Annotated[Path, typer.Option('--output', '-o', help='Output file path for the JSON report (default: server-info.json)')] = Path('server-info.json')) -> None
|
||||
```
|
||||
|
||||
|
||||
Inspect a FastMCP server and generate a JSON report.
|
||||
|
||||
This command analyzes a FastMCP server (v1.x or v2.x) and generates
|
||||
a comprehensive JSON report containing information about the server's
|
||||
name, instructions, version, tools, prompts, resources, templates,
|
||||
and capabilities.
|
||||
|
||||
**Examples:**
|
||||
|
||||
fastmcp inspect server.py
|
||||
fastmcp inspect server.py -o report.json
|
||||
fastmcp inspect server.py:mcp -o analysis.json
|
||||
fastmcp inspect path/to/server.py:app -o /tmp/server-info.json
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ FastMCP run command implementation.
|
|||
|
||||
## Functions
|
||||
|
||||
### `is_url`
|
||||
### `is_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L14"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
is_url(path: str) -> bool
|
||||
|
|
@ -20,7 +20,7 @@ is_url(path: str) -> bool
|
|||
Check if a string is a URL.
|
||||
|
||||
|
||||
### `parse_file_path`
|
||||
### `parse_file_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L20"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
parse_file_path(server_spec: str) -> tuple[Path, str | None]
|
||||
|
|
@ -36,7 +36,7 @@ Parse a file path that may include a server object specification.
|
|||
- Tuple of (file_path, server_object)
|
||||
|
||||
|
||||
### `import_server`
|
||||
### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L51"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
import_server(file: Path, server_object: str | None = None) -> Any
|
||||
|
|
@ -53,7 +53,7 @@ Import a MCP server from a file.
|
|||
- The server object
|
||||
|
||||
|
||||
### `create_client_server`
|
||||
### `create_client_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L121"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_client_server(url: str) -> Any
|
||||
|
|
@ -69,7 +69,7 @@ Create a FastMCP server from a client URL.
|
|||
- A FastMCP server instance
|
||||
|
||||
|
||||
### `import_server_with_args`
|
||||
### `import_server_with_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L141"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
import_server_with_args(file: Path, server_object: str | None = None, server_args: list[str] | None = None) -> Any
|
||||
|
|
@ -87,7 +87,7 @@ Import a server with optional command line arguments.
|
|||
- The imported server object
|
||||
|
||||
|
||||
### `run_command`
|
||||
### `run_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L165"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
run_command(server_spec: str, transport: str | None = None, host: str | None = None, port: int | None = None, log_level: str | None = None, server_args: list[str] | None = None) -> None
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ sidebarTitle: bearer
|
|||
|
||||
## Classes
|
||||
|
||||
### `BearerAuth`
|
||||
### `BearerAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/bearer.py#L11"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `auth_flow`
|
||||
#### `auth_flow` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/bearer.py#L15"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
auth_flow(self, request)
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ sidebarTitle: oauth
|
|||
|
||||
## Functions
|
||||
|
||||
### `default_cache_dir`
|
||||
### `default_cache_dir` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L38"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
default_cache_dir() -> Path
|
||||
```
|
||||
|
||||
### `OAuth`
|
||||
### `OAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L295"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
OAuth(mcp_url: str, scopes: str | list[str] | None = None, client_name: str = 'FastMCP Client', token_storage_cache_dir: Path | None = None, additional_client_metadata: dict[str, Any] | None = None) -> _MCPOAuthClientProvider
|
||||
|
|
@ -38,7 +38,7 @@ httpx.AsyncClient (or appropriate FastMCP client/transport instance)
|
|||
|
||||
## Classes
|
||||
|
||||
### `ServerOAuthMetadata`
|
||||
### `ServerOAuthMetadata` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L43"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
More flexible OAuth metadata model that accepts broader ranges of values
|
||||
|
|
@ -48,13 +48,13 @@ This handles real-world OAuth servers like PayPal that may support
|
|||
additional methods not in the MCP specification.
|
||||
|
||||
|
||||
### `OAuthClientProvider`
|
||||
### `OAuthClientProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L68"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
OAuth client provider with more flexible OAuth metadata discovery.
|
||||
|
||||
|
||||
### `FileTokenStorage`
|
||||
### `FileTokenStorage` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L116"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
File-based token storage implementation for OAuth credentials and tokens.
|
||||
|
|
@ -65,7 +65,7 @@ Each instance is tied to a specific server URL for proper token isolation.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_base_url`
|
||||
#### `get_base_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L131"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_base_url(url: str) -> str
|
||||
|
|
@ -74,7 +74,7 @@ get_base_url(url: str) -> str
|
|||
Extract the base URL (scheme + host) from a URL.
|
||||
|
||||
|
||||
#### `get_cache_key`
|
||||
#### `get_cache_key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L136"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_cache_key(self) -> str
|
||||
|
|
@ -83,7 +83,7 @@ get_cache_key(self) -> str
|
|||
Generate a safe filesystem key from the server's base URL.
|
||||
|
||||
|
||||
#### `clear`
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L208"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
clear(self) -> None
|
||||
|
|
@ -92,7 +92,7 @@ clear(self) -> None
|
|||
Clear all cached data for this server.
|
||||
|
||||
|
||||
#### `clear_all`
|
||||
#### `clear_all` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L217"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
clear_all(cls, cache_dir: Path | None = None) -> None
|
||||
|
|
|
|||
|
|
@ -7,48 +7,48 @@ sidebarTitle: client
|
|||
|
||||
## Classes
|
||||
|
||||
### `Client`
|
||||
### `Client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L60"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
MCP client that delegates connection management to a Transport instance.
|
||||
|
||||
MCP client that delegates connection management to a Transport instance.
|
||||
The Client class is responsible for MCP protocol logic, while the Transport
|
||||
handles connection establishment and management. Client provides methods for
|
||||
working with resources, prompts, tools and other MCP capabilities.
|
||||
|
||||
The Client class is responsible for MCP protocol logic, while the Transport
|
||||
handles connection establishment and management. Client provides methods for
|
||||
working with resources, prompts, tools and other MCP capabilities.
|
||||
**Args:**
|
||||
- `transport`: Connection source specification, which can be\:
|
||||
- ClientTransport\: Direct transport instance
|
||||
- FastMCP\: In-process FastMCP server
|
||||
- AnyUrl | str\: URL to connect to
|
||||
- Path\: File path for local socket
|
||||
- MCPConfig\: MCP server configuration
|
||||
- dict\: Transport configuration
|
||||
- `roots`: Optional RootsList or RootsHandler for filesystem access
|
||||
- `sampling_handler`: Optional handler for sampling requests
|
||||
- `log_handler`: Optional handler for log messages
|
||||
- `message_handler`: Optional handler for protocol messages
|
||||
- `progress_handler`: Optional handler for progress notifications
|
||||
- `timeout`: Optional timeout for requests (seconds or timedelta)
|
||||
- `init_timeout`: Optional timeout for initial connection (seconds or timedelta).
|
||||
Set to 0 to disable. If None, uses the value in the FastMCP global settings.
|
||||
|
||||
Args:
|
||||
transport: Connection source specification, which can be:
|
||||
- ClientTransport: Direct transport instance
|
||||
- FastMCP: In-process FastMCP server
|
||||
- AnyUrl | str: URL to connect to
|
||||
- Path: File path for local socket
|
||||
- MCPConfig: MCP server configuration
|
||||
- dict: Transport configuration
|
||||
roots: Optional RootsList or RootsHandler for filesystem access
|
||||
sampling_handler: Optional handler for sampling requests
|
||||
log_handler: Optional handler for log messages
|
||||
message_handler: Optional handler for protocol messages
|
||||
progress_handler: Optional handler for progress notifications
|
||||
timeout: Optional timeout for requests (seconds or timedelta)
|
||||
init_timeout: Optional timeout for initial connection (seconds or timedelta).
|
||||
Set to 0 to disable. If None, uses the value in the FastMCP global settings.
|
||||
**Examples:**
|
||||
|
||||
Examples:
|
||||
```python # Connect to FastMCP server client =
|
||||
Client("http://localhost:8080")
|
||||
```python # Connect to FastMCP server client =
|
||||
Client("http://localhost:8080")
|
||||
|
||||
async with client:
|
||||
# List available resources resources = await client.list_resources()
|
||||
async with client:
|
||||
# List available resources resources = await client.list_resources()
|
||||
|
||||
# Call a tool result = await client.call_tool("my_tool", {"param":
|
||||
"value"})
|
||||
```
|
||||
|
||||
# Call a tool result = await client.call_tool("my_tool", {"param":
|
||||
"value"})
|
||||
```
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `session`
|
||||
#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L207"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
session(self) -> ClientSession
|
||||
|
|
@ -57,7 +57,7 @@ session(self) -> ClientSession
|
|||
Get the current active session. Raises RuntimeError if not connected.
|
||||
|
||||
|
||||
#### `initialize_result`
|
||||
#### `initialize_result` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L217"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
initialize_result(self) -> mcp.types.InitializeResult
|
||||
|
|
@ -66,7 +66,7 @@ initialize_result(self) -> mcp.types.InitializeResult
|
|||
Get the result of the initialization request.
|
||||
|
||||
|
||||
#### `set_roots`
|
||||
#### `set_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L225"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set_roots(self, roots: RootsList | RootsHandler) -> None
|
||||
|
|
@ -75,7 +75,7 @@ set_roots(self, roots: RootsList | RootsHandler) -> None
|
|||
Set the roots for the client. This does not automatically call `send_roots_list_changed`.
|
||||
|
||||
|
||||
#### `set_sampling_callback`
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L229"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set_sampling_callback(self, sampling_callback: SamplingHandler) -> None
|
||||
|
|
@ -84,7 +84,7 @@ set_sampling_callback(self, sampling_callback: SamplingHandler) -> None
|
|||
Set the sampling callback for the client.
|
||||
|
||||
|
||||
#### `is_connected`
|
||||
#### `is_connected` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L235"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
is_connected(self) -> bool
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: logging
|
|||
|
||||
## Functions
|
||||
|
||||
### `create_log_callback`
|
||||
### `create_log_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/logging.py#L20"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_log_callback(handler: LogHandler | None = None) -> LoggingFnT
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ and display styled responses to users.
|
|||
|
||||
## Functions
|
||||
|
||||
### `create_callback_html`
|
||||
### `create_callback_html` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L25"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_callback_html(message: str, is_success: bool = True, title: str = 'FastMCP OAuth', server_url: str | None = None) -> str
|
||||
|
|
@ -25,7 +25,7 @@ create_callback_html(message: str, is_success: bool = True, title: str = 'FastMC
|
|||
Create a styled HTML response for OAuth callbacks.
|
||||
|
||||
|
||||
### `create_oauth_callback_server`
|
||||
### `create_oauth_callback_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L197"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_oauth_callback_server(port: int, callback_path: str = '/callback', server_url: str | None = None, response_future: asyncio.Future | None = None) -> Server
|
||||
|
|
@ -46,17 +46,17 @@ Create an OAuth callback server.
|
|||
|
||||
## Classes
|
||||
|
||||
### `CallbackResponse`
|
||||
### `CallbackResponse` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L183"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `from_dict`
|
||||
#### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L190"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_dict(cls, data: dict[str, str]) -> CallbackResponse
|
||||
```
|
||||
|
||||
#### `to_dict`
|
||||
#### `to_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/oauth_callback.py#L193"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_dict(self) -> dict[str, str]
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ sidebarTitle: roots
|
|||
|
||||
## Functions
|
||||
|
||||
### `convert_roots_list`
|
||||
### `convert_roots_list` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/roots.py#L19"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
convert_roots_list(roots: RootsList) -> list[mcp.types.Root]
|
||||
```
|
||||
|
||||
### `create_roots_callback`
|
||||
### `create_roots_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/roots.py#L33"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_roots_callback(handler: RootsList | RootsHandler) -> ListRootsFnT
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: sampling
|
|||
|
||||
## Functions
|
||||
|
||||
### `create_sampling_callback`
|
||||
### `create_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/sampling.py#L25"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_sampling_callback(sampling_handler: SamplingHandler) -> SamplingFnT
|
||||
|
|
|
|||
|
|
@ -7,63 +7,63 @@ sidebarTitle: transports
|
|||
|
||||
## Functions
|
||||
|
||||
### `infer_transport`
|
||||
### `infer_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L837"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
infer_transport(transport: ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str) -> ClientTransport
|
||||
```
|
||||
|
||||
|
||||
Infer the appropriate transport type from the given transport argument.
|
||||
|
||||
Infer the appropriate transport type from the given transport argument.
|
||||
This function attempts to infer the correct transport type from the provided
|
||||
argument, handling various input types and converting them to the appropriate
|
||||
ClientTransport subclass.
|
||||
|
||||
This function attempts to infer the correct transport type from the provided
|
||||
argument, handling various input types and converting them to the appropriate
|
||||
ClientTransport subclass.
|
||||
The function supports these input types:
|
||||
- ClientTransport: Used directly without modification
|
||||
- FastMCP or FastMCP1Server: Creates an in-memory FastMCPTransport
|
||||
- Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
|
||||
- AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
|
||||
- MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers
|
||||
|
||||
The function supports these input types:
|
||||
- ClientTransport: Used directly without modification
|
||||
- FastMCP or FastMCP1Server: Creates an in-memory FastMCPTransport
|
||||
- Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
|
||||
- AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
|
||||
- MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers
|
||||
For HTTP URLs, they are assumed to be Streamable HTTP URLs unless they end in `/sse`.
|
||||
|
||||
For HTTP URLs, they are assumed to be Streamable HTTP URLs unless they end in `/sse`.
|
||||
For MCPConfig with multiple servers, a composite client is created where each server
|
||||
is mounted with its name as prefix. This allows accessing tools and resources from multiple
|
||||
servers through a single unified client interface, using naming patterns like
|
||||
`servername_toolname` for tools and `protocol://servername/path` for resources.
|
||||
If the MCPConfig contains only one server, a direct connection is established without prefixing.
|
||||
|
||||
For MCPConfig with multiple servers, a composite client is created where each server
|
||||
is mounted with its name as prefix. This allows accessing tools and resources from multiple
|
||||
servers through a single unified client interface, using naming patterns like
|
||||
`servername_toolname` for tools and `protocol://servername/path` for resources.
|
||||
If the MCPConfig contains only one server, a direct connection is established without prefixing.
|
||||
**Examples:**
|
||||
|
||||
Examples:
|
||||
```python
|
||||
# Connect to a local Python script
|
||||
transport = infer_transport("my_script.py")
|
||||
```python
|
||||
# Connect to a local Python script
|
||||
transport = infer_transport("my_script.py")
|
||||
|
||||
# Connect to a remote server via HTTP
|
||||
transport = infer_transport("http://example.com/mcp")
|
||||
# Connect to a remote server via HTTP
|
||||
transport = infer_transport("http://example.com/mcp")
|
||||
|
||||
# Connect to multiple servers using MCPConfig
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {"url": "http://weather.example.com/mcp"},
|
||||
"calendar": {"url": "http://calendar.example.com/mcp"}
|
||||
}
|
||||
}
|
||||
transport = infer_transport(config)
|
||||
```
|
||||
|
||||
# Connect to multiple servers using MCPConfig
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {"url": "http://weather.example.com/mcp"},
|
||||
"calendar": {"url": "http://calendar.example.com/mcp"}
|
||||
}
|
||||
}
|
||||
transport = infer_transport(config)
|
||||
```
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `SessionKwargs`
|
||||
### `SessionKwargs` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L52"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Keyword arguments for the MCP ClientSession constructor.
|
||||
|
||||
|
||||
### `ClientTransport`
|
||||
### `ClientTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L63"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Abstract base class for different MCP client transport mechanisms.
|
||||
|
|
@ -72,25 +72,25 @@ A Transport is responsible for establishing and managing connections
|
|||
to an MCP server, and providing a ClientSession within an async context.
|
||||
|
||||
|
||||
### `WSTransport`
|
||||
### `WSTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L109"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via WebSockets.
|
||||
|
||||
|
||||
### `SSETransport`
|
||||
### `SSETransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L148"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via Server-Sent Events.
|
||||
|
||||
|
||||
### `StreamableHttpTransport`
|
||||
### `StreamableHttpTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L223"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via Streamable HTTP Requests.
|
||||
|
||||
|
||||
### `StdioTransport`
|
||||
### `StdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L299"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Base transport for connecting to an MCP server via subprocess with stdio.
|
||||
|
|
@ -99,37 +99,37 @@ This is a base class that can be subclassed for specific command-based
|
|||
transports like Python, Node, Uvx, etc.
|
||||
|
||||
|
||||
### `PythonStdioTransport`
|
||||
### `PythonStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L416"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport for running Python scripts.
|
||||
|
||||
|
||||
### `FastMCPStdioTransport`
|
||||
### `FastMCPStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L462"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport for running FastMCP servers using the FastMCP CLI.
|
||||
|
||||
|
||||
### `NodeStdioTransport`
|
||||
### `NodeStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L489"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport for running Node.js scripts.
|
||||
|
||||
|
||||
### `UvxStdioTransport`
|
||||
### `UvxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L531"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the uvx tool.
|
||||
|
||||
|
||||
### `NpxStdioTransport`
|
||||
### `NpxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L597"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the npx tool.
|
||||
|
||||
|
||||
### `FastMCPTransport`
|
||||
### `FastMCPTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L659"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
In-memory transport for FastMCP servers.
|
||||
|
|
@ -140,52 +140,53 @@ servers from the low-level MCP SDK. This is particularly useful for unit
|
|||
tests or scenarios where client and server run in the same runtime.
|
||||
|
||||
|
||||
### `MCPConfigTransport`
|
||||
### `MCPConfigTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L713"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Transport for connecting to one or more MCP servers defined in an MCPConfig.
|
||||
|
||||
This transport provides a unified interface to multiple MCP servers defined in an MCPConfig
|
||||
object or dictionary matching the MCPConfig schema. It supports two key scenarios:
|
||||
This transport provides a unified interface to multiple MCP servers defined in an MCPConfig
|
||||
object or dictionary matching the MCPConfig schema. It supports two key scenarios:
|
||||
|
||||
1. If the MCPConfig contains exactly one server, it creates a direct transport to that server.
|
||||
2. If the MCPConfig contains multiple servers, it creates a composite client by mounting
|
||||
all servers on a single FastMCP instance, with each server's name used as its mounting prefix.
|
||||
1. If the MCPConfig contains exactly one server, it creates a direct transport to that server.
|
||||
2. If the MCPConfig contains multiple servers, it creates a composite client by mounting
|
||||
all servers on a single FastMCP instance, with each server's name used as its mounting prefix.
|
||||
|
||||
In the multi-server case, tools are accessible with the prefix pattern `{server_name}_{tool_name}`
|
||||
and resources with the pattern `protocol://{server_name}/path/to/resource`.
|
||||
In the multi-server case, tools are accessible with the prefix pattern `{server_name}_{tool_name}`
|
||||
and resources with the pattern `protocol://{server_name}/path/to/resource`.
|
||||
|
||||
This is particularly useful for creating clients that need to interact with multiple specialized
|
||||
MCP servers through a single interface, simplifying client code.
|
||||
This is particularly useful for creating clients that need to interact with multiple specialized
|
||||
MCP servers through a single interface, simplifying client code.
|
||||
|
||||
Examples:
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.utilities.mcp_config import MCPConfig
|
||||
**Examples:**
|
||||
|
||||
# Create a config with multiple servers
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather-api.example.com/mcp",
|
||||
"transport": "streamable-http"
|
||||
},
|
||||
"calendar": {
|
||||
"url": "https://calendar-api.example.com/mcp",
|
||||
"transport": "streamable-http"
|
||||
}
|
||||
}
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.utilities.mcp_config import MCPConfig
|
||||
|
||||
# Create a config with multiple servers
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather-api.example.com/mcp",
|
||||
"transport": "http"
|
||||
},
|
||||
"calendar": {
|
||||
"url": "https://calendar-api.example.com/mcp",
|
||||
"transport": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a client with the config
|
||||
client = Client(config)
|
||||
# Create a client with the config
|
||||
client = Client(config)
|
||||
|
||||
async with client:
|
||||
# Access tools with prefixes
|
||||
weather = await client.call_tool("weather_get_forecast", {"city": "London"})
|
||||
events = await client.call_tool("calendar_list_events", {"date": "2023-06-01"})
|
||||
async with client:
|
||||
# Access tools with prefixes
|
||||
weather = await client.call_tool("weather_get_forecast", {"city": "London"})
|
||||
events = await client.call_tool("calendar_list_events", {"date": "2023-06-01"})
|
||||
|
||||
# Access resources with prefixed URIs
|
||||
icons = await client.read_resource("weather://weather/icons/sunny")
|
||||
```
|
||||
|
||||
# Access resources with prefixed URIs
|
||||
icons = await client.read_resource("weather://weather/icons/sunny")
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -10,55 +10,55 @@ Custom exceptions for FastMCP.
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCPError`
|
||||
### `FastMCPError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L6"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Base error for FastMCP.
|
||||
|
||||
|
||||
### `ValidationError`
|
||||
### `ValidationError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L10"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Error in validating parameters or return values.
|
||||
|
||||
|
||||
### `ResourceError`
|
||||
### `ResourceError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L14"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Error in resource operations.
|
||||
|
||||
|
||||
### `ToolError`
|
||||
### `ToolError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L18"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Error in tool operations.
|
||||
|
||||
|
||||
### `PromptError`
|
||||
### `PromptError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L22"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Error in prompt operations.
|
||||
|
||||
|
||||
### `InvalidSignature`
|
||||
### `InvalidSignature` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L26"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Invalid signature for use with FastMCP.
|
||||
|
||||
|
||||
### `ClientError`
|
||||
### `ClientError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L30"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Error in client operations.
|
||||
|
||||
|
||||
### `NotFoundError`
|
||||
### `NotFoundError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L34"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Object not found.
|
||||
|
||||
|
||||
### `DisabledError`
|
||||
### `DisabledError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/exceptions.py#L38"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Object is disabled.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Base classes for FastMCP prompts.
|
|||
|
||||
## Functions
|
||||
|
||||
### `Message`
|
||||
### `Message` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L32"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
Message(content: str | MCPContent, role: Role | None = None, **kwargs: Any) -> PromptMessage
|
||||
|
|
@ -22,13 +22,13 @@ A user-friendly constructor for PromptMessage.
|
|||
|
||||
## Classes
|
||||
|
||||
### `PromptArgument`
|
||||
### `PromptArgument` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L54"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
An argument that can be passed to a prompt.
|
||||
|
||||
|
||||
### `Prompt`
|
||||
### `Prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L66"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A prompt template that can be rendered with parameters.
|
||||
|
|
@ -36,7 +36,7 @@ A prompt template that can be rendered with parameters.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_mcp_prompt`
|
||||
#### `to_mcp_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L73"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_mcp_prompt(self, **overrides: Any) -> MCPPrompt
|
||||
|
|
@ -45,7 +45,7 @@ to_mcp_prompt(self, **overrides: Any) -> MCPPrompt
|
|||
Convert the prompt to an MCP prompt.
|
||||
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L91"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionPrompt
|
||||
|
|
@ -60,7 +60,7 @@ The function can return:
|
|||
- A sequence of any of the above
|
||||
|
||||
|
||||
### `FunctionPrompt`
|
||||
### `FunctionPrompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L119"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A prompt that is a function.
|
||||
|
|
@ -68,7 +68,7 @@ A prompt that is a function.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L125"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionPrompt
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: prompt_manager
|
|||
|
||||
## Classes
|
||||
|
||||
### `PromptManager`
|
||||
### `PromptManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt_manager.py#L21"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Manages FastMCP prompts.
|
||||
|
|
@ -15,7 +15,7 @@ Manages FastMCP prompts.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `mount`
|
||||
#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt_manager.py#L45"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
mount(self, server: MountedServer) -> None
|
||||
|
|
@ -24,7 +24,7 @@ mount(self, server: MountedServer) -> None
|
|||
Adds a mounted server as a source for prompts.
|
||||
|
||||
|
||||
#### `add_prompt_from_fn`
|
||||
#### `add_prompt_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt_manager.py#L114"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_prompt_from_fn(self, fn: Callable[..., PromptResult | Awaitable[PromptResult]], name: str | None = None, description: str | None = None, tags: set[str] | None = None) -> FunctionPrompt
|
||||
|
|
@ -33,7 +33,7 @@ add_prompt_from_fn(self, fn: Callable[..., PromptResult | Awaitable[PromptResult
|
|||
Create a prompt from a function.
|
||||
|
||||
|
||||
#### `add_prompt`
|
||||
#### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt_manager.py#L134"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_prompt(self, prompt: Prompt) -> Prompt
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Base classes and interfaces for FastMCP resources.
|
|||
|
||||
## Classes
|
||||
|
||||
### `Resource`
|
||||
### `Resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L32"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Base class for all resources.
|
||||
|
|
@ -18,13 +18,13 @@ Base class for all resources.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L48"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(fn: Callable[[], Any], uri: str | AnyUrl, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResource
|
||||
```
|
||||
|
||||
#### `set_default_mime_type`
|
||||
#### `set_default_mime_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L69"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set_default_mime_type(cls, mime_type: str | None) -> str
|
||||
|
|
@ -33,7 +33,7 @@ set_default_mime_type(cls, mime_type: str | None) -> str
|
|||
Set default MIME type if not provided.
|
||||
|
||||
|
||||
#### `set_default_name`
|
||||
#### `set_default_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L76"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set_default_name(self) -> Self
|
||||
|
|
@ -42,7 +42,7 @@ set_default_name(self) -> Self
|
|||
Set default name from URI if not provided.
|
||||
|
||||
|
||||
#### `to_mcp_resource`
|
||||
#### `to_mcp_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L91"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_mcp_resource(self, **overrides: Any) -> MCPResource
|
||||
|
|
@ -51,7 +51,7 @@ to_mcp_resource(self, **overrides: Any) -> MCPResource
|
|||
Convert the resource to an MCPResource.
|
||||
|
||||
|
||||
#### `key`
|
||||
#### `key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L105"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
key(self) -> str
|
||||
|
|
@ -63,7 +63,7 @@ keys having a certain value, as the same tool loaded from different
|
|||
hierarchies of servers may have different keys.
|
||||
|
||||
|
||||
### `FunctionResource`
|
||||
### `FunctionResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L115"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A resource that defers data loading by wrapping a function.
|
||||
|
|
@ -80,7 +80,7 @@ The function can return:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource.py#L131"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[[], Any], uri: str | AnyUrl, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResource
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Resource manager functionality.
|
|||
|
||||
## Classes
|
||||
|
||||
### `ResourceManager`
|
||||
### `ResourceManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L28"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Manages FastMCP resources.
|
||||
|
|
@ -18,7 +18,7 @@ Manages FastMCP resources.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `mount`
|
||||
#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L60"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
mount(self, server: MountedServer) -> None
|
||||
|
|
@ -27,7 +27,7 @@ mount(self, server: MountedServer) -> None
|
|||
Adds a mounted server as a source for resources and templates.
|
||||
|
||||
|
||||
#### `add_resource_or_template_from_fn`
|
||||
#### `add_resource_or_template_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L182"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource_or_template_from_fn(self, fn: Callable[..., Any], uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> Resource | ResourceTemplate
|
||||
|
|
@ -48,7 +48,7 @@ Add a resource or template to the manager from a function.
|
|||
- returns the existing resource or template.
|
||||
|
||||
|
||||
#### `add_resource_from_fn`
|
||||
#### `add_resource_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L230"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource_from_fn(self, fn: Callable[..., Any], uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> Resource
|
||||
|
|
@ -69,7 +69,7 @@ Add a resource to the manager from a function.
|
|||
- returns the existing resource.
|
||||
|
||||
|
||||
#### `add_resource`
|
||||
#### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L270"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource(self, resource: Resource) -> Resource
|
||||
|
|
@ -83,7 +83,7 @@ will be used as the storage key. To overwrite it, call
|
|||
Resource.with_key() before calling this method.
|
||||
|
||||
|
||||
#### `add_template_from_fn`
|
||||
#### `add_template_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L292"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_template_from_fn(self, fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> ResourceTemplate
|
||||
|
|
@ -92,7 +92,7 @@ add_template_from_fn(self, fn: Callable[..., Any], uri_template: str, name: str
|
|||
Create a template from a function.
|
||||
|
||||
|
||||
#### `add_template`
|
||||
#### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/resource_manager.py#L319"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_template(self, template: ResourceTemplate) -> ResourceTemplate
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ Resource template functionality.
|
|||
|
||||
## Functions
|
||||
|
||||
### `build_regex`
|
||||
### `build_regex` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L28"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
build_regex(template: str) -> re.Pattern
|
||||
```
|
||||
|
||||
### `match_uri_template`
|
||||
### `match_uri_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L44"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None
|
||||
|
|
@ -24,7 +24,7 @@ match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None
|
|||
|
||||
## Classes
|
||||
|
||||
### `ResourceTemplate`
|
||||
### `ResourceTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L52"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A template for dynamically creating resources.
|
||||
|
|
@ -32,13 +32,13 @@ A template for dynamically creating resources.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L69"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResourceTemplate
|
||||
```
|
||||
|
||||
#### `set_default_mime_type`
|
||||
#### `set_default_mime_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L90"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set_default_mime_type(cls, mime_type: str | None) -> str
|
||||
|
|
@ -47,7 +47,7 @@ set_default_mime_type(cls, mime_type: str | None) -> str
|
|||
Set default MIME type if not provided.
|
||||
|
||||
|
||||
#### `matches`
|
||||
#### `matches` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L96"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
matches(self, uri: str) -> dict[str, Any] | None
|
||||
|
|
@ -56,7 +56,7 @@ matches(self, uri: str) -> dict[str, Any] | None
|
|||
Check if URI matches template and extract parameters.
|
||||
|
||||
|
||||
#### `to_mcp_template`
|
||||
#### `to_mcp_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L124"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate
|
||||
|
|
@ -65,7 +65,7 @@ to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate
|
|||
Convert the resource template to an MCPResourceTemplate.
|
||||
|
||||
|
||||
#### `from_mcp_template`
|
||||
#### `from_mcp_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L135"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_template(cls, mcp_template: MCPResourceTemplate) -> ResourceTemplate
|
||||
|
|
@ -74,7 +74,7 @@ from_mcp_template(cls, mcp_template: MCPResourceTemplate) -> ResourceTemplate
|
|||
Creates a FastMCP ResourceTemplate from a raw MCP ResourceTemplate object.
|
||||
|
||||
|
||||
#### `key`
|
||||
#### `key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L148"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
key(self) -> str
|
||||
|
|
@ -86,7 +86,7 @@ keys having a certain value, as the same tool loaded from different
|
|||
hierarchies of servers may have different keys.
|
||||
|
||||
|
||||
### `FunctionResourceTemplate`
|
||||
### `FunctionResourceTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L158"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A template for dynamically creating resources.
|
||||
|
|
@ -94,7 +94,7 @@ A template for dynamically creating resources.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/template.py#L179"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[..., Any], uri_template: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None, enabled: bool | None = None) -> FunctionResourceTemplate
|
||||
|
|
|
|||
|
|
@ -10,19 +10,19 @@ Concrete resource implementations.
|
|||
|
||||
## Classes
|
||||
|
||||
### `TextResource`
|
||||
### `TextResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L21"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A resource that reads from a string.
|
||||
|
||||
|
||||
### `BinaryResource`
|
||||
### `BinaryResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L31"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A resource that reads from bytes.
|
||||
|
||||
|
||||
### `FileResource`
|
||||
### `FileResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L41"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A resource that reads from a file.
|
||||
|
|
@ -32,7 +32,7 @@ Set is_binary=True to read file as binary data instead of text.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `validate_absolute_path`
|
||||
#### `validate_absolute_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L59"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_absolute_path(cls, path: Path) -> Path
|
||||
|
|
@ -41,7 +41,7 @@ validate_absolute_path(cls, path: Path) -> Path
|
|||
Ensure path is absolute.
|
||||
|
||||
|
||||
#### `set_binary_from_mime_type`
|
||||
#### `set_binary_from_mime_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L67"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> bool
|
||||
|
|
@ -50,13 +50,13 @@ set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> bool
|
|||
Set is_binary based on mime_type if not explicitly set.
|
||||
|
||||
|
||||
### `HttpResource`
|
||||
### `HttpResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L84"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A resource that reads from an HTTP endpoint.
|
||||
|
||||
|
||||
### `DirectoryResource`
|
||||
### `DirectoryResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L100"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A resource that lists files in a directory.
|
||||
|
|
@ -64,7 +64,7 @@ A resource that lists files in a directory.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `validate_absolute_path`
|
||||
#### `validate_absolute_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L116"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_absolute_path(cls, path: Path) -> Path
|
||||
|
|
@ -73,7 +73,7 @@ validate_absolute_path(cls, path: Path) -> Path
|
|||
Ensure path is absolute.
|
||||
|
||||
|
||||
#### `list_files`
|
||||
#### `list_files` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/resources/types.py#L122"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
list_files(self) -> list[Path]
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ sidebarTitle: auth
|
|||
|
||||
## Classes
|
||||
|
||||
### `OAuthProvider`
|
||||
### `OAuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L14"><Icon icon="github" size="14" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -7,23 +7,23 @@ sidebarTitle: bearer
|
|||
|
||||
## Classes
|
||||
|
||||
### `JWKData`
|
||||
### `JWKData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L29"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
JSON Web Key data structure.
|
||||
|
||||
|
||||
### `JWKSData`
|
||||
### `JWKSData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L42"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
JSON Web Key Set data structure.
|
||||
|
||||
|
||||
### `RSAKeyPair`
|
||||
### `RSAKeyPair` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L49"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `generate`
|
||||
#### `generate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L54"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
generate(cls) -> 'RSAKeyPair'
|
||||
|
|
@ -35,7 +35,7 @@ Generate an RSA key pair for testing.
|
|||
- (private_key_pem, public_key_pem)
|
||||
|
||||
|
||||
#### `create_token`
|
||||
#### `create_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L88"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_token(self, subject: str = 'fastmcp-user', issuer: str = 'https://fastmcp.example.com', audience: str | list[str] | None = None, scopes: list[str] | None = None, expires_in_seconds: int = 3600, additional_claims: dict[str, Any] | None = None, kid: str | None = None) -> str
|
||||
|
|
@ -57,7 +57,7 @@ Generate a test JWT token for testing purposes.
|
|||
- Signed JWT token string
|
||||
|
||||
|
||||
### `BearerAuthProvider`
|
||||
### `BearerAuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer.py#L149"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Simple JWT Bearer Token validator for hosted MCP servers.
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ sidebarTitle: bearer_env
|
|||
|
||||
## Classes
|
||||
|
||||
### `EnvBearerAuthProviderSettings`
|
||||
### `EnvBearerAuthProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer_env.py#L8"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Settings for the BearerAuthProvider.
|
||||
|
||||
|
||||
### `EnvBearerAuthProvider`
|
||||
### `EnvBearerAuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/bearer_env.py#L24"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A BearerAuthProvider that loads settings from environment variables. Any
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: in_memory
|
|||
|
||||
## Classes
|
||||
|
||||
### `InMemoryOAuthProvider`
|
||||
### `InMemoryOAuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L31"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
An in-memory OAuth provider for testing purposes.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: context
|
|||
|
||||
## Functions
|
||||
|
||||
### `set_context`
|
||||
### `set_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L36"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set_context(context: Context) -> Generator[Context, None, None]
|
||||
|
|
@ -15,7 +15,7 @@ set_context(context: Context) -> Generator[Context, None, None]
|
|||
|
||||
## Classes
|
||||
|
||||
### `Context`
|
||||
### `Context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L45"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Context object providing access to MCP capabilities.
|
||||
|
|
@ -53,7 +53,7 @@ The context is optional - tools that don't need it can omit the parameter.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `request_context`
|
||||
#### `request_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L98"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
request_context(self) -> RequestContext
|
||||
|
|
@ -64,7 +64,7 @@ Access to the underlying request context.
|
|||
If called outside of a request context, this will raise a ValueError.
|
||||
|
||||
|
||||
#### `client_id`
|
||||
#### `client_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L168"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
client_id(self) -> str | None
|
||||
|
|
@ -73,7 +73,7 @@ client_id(self) -> str | None
|
|||
Get the client ID if available.
|
||||
|
||||
|
||||
#### `request_id`
|
||||
#### `request_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L177"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
request_id(self) -> str
|
||||
|
|
@ -82,7 +82,7 @@ request_id(self) -> str
|
|||
Get the unique ID for this request.
|
||||
|
||||
|
||||
#### `session_id`
|
||||
#### `session_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L182"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
session_id(self) -> str | None
|
||||
|
|
@ -99,7 +99,7 @@ the same client session.
|
|||
- for stdio and in-memory transports which don't use session IDs.
|
||||
|
||||
|
||||
#### `session`
|
||||
#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L213"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
session(self)
|
||||
|
|
@ -108,7 +108,7 @@ session(self)
|
|||
Access to the underlying session for advanced usage.
|
||||
|
||||
|
||||
#### `get_http_request`
|
||||
#### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L282"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_request(self) -> Request
|
||||
|
|
|
|||
|
|
@ -7,19 +7,19 @@ sidebarTitle: dependencies
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_context`
|
||||
### `get_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L27"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_context() -> Context
|
||||
```
|
||||
|
||||
### `get_http_request`
|
||||
### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L39"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_request() -> Request
|
||||
```
|
||||
|
||||
### `get_http_headers`
|
||||
### `get_http_headers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L48"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_headers(include_all: bool = False) -> dict[str, str]
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ sidebarTitle: http
|
|||
|
||||
## Functions
|
||||
|
||||
### `set_http_request`
|
||||
### `set_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L48"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set_http_request(request: Request) -> Generator[Request, None, None]
|
||||
```
|
||||
|
||||
### `setup_auth_middleware_and_routes`
|
||||
### `setup_auth_middleware_and_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L72"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
setup_auth_middleware_and_routes(auth: OAuthProvider) -> tuple[list[Middleware], list[BaseRoute], list[str]]
|
||||
|
|
@ -29,7 +29,7 @@ Set up authentication middleware and routes if auth is enabled.
|
|||
- Tuple of (middleware, auth_routes, required_scopes)
|
||||
|
||||
|
||||
### `create_base_app`
|
||||
### `create_base_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L110"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_base_app(routes: list[BaseRoute], middleware: list[Middleware], debug: bool = False, lifespan: Callable | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -48,7 +48,7 @@ Create a base Starlette app with common middleware and routes.
|
|||
- A Starlette application
|
||||
|
||||
|
||||
### `create_sse_app`
|
||||
### `create_sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L138"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_sse_app(server: FastMCP[LifespanResultT], message_path: str, sse_path: str, auth: OAuthProvider | None = None, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -70,7 +70,7 @@ Returns:
|
|||
A Starlette application with RequestContextMiddleware
|
||||
|
||||
|
||||
### `create_streamable_http_app`
|
||||
### `create_streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L246"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
create_streamable_http_app(server: FastMCP[LifespanResultT], streamable_http_path: str, event_store: EventStore | None = None, auth: OAuthProvider | None = None, json_response: bool = False, stateless_http: bool = False, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -96,17 +96,17 @@ Return an instance of the StreamableHTTP server app.
|
|||
|
||||
## Classes
|
||||
|
||||
### `StarletteWithLifespan`
|
||||
### `StarletteWithLifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L41"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `lifespan`
|
||||
#### `lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L43"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
lifespan(self) -> Lifespan
|
||||
```
|
||||
|
||||
### `RequestContextMiddleware`
|
||||
### `RequestContextMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L56"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Middleware that stores each request in a ContextVar
|
||||
|
|
|
|||
8
docs/python-sdk/fastmcp-server-middleware-__init__.mdx
Normal file
8
docs/python-sdk/fastmcp-server-middleware-__init__.mdx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: __init__
|
||||
sidebarTitle: __init__
|
||||
---
|
||||
|
||||
# `fastmcp.server.middleware`
|
||||
|
||||
*This module is empty or contains only private/internal implementations.*
|
||||
40
docs/python-sdk/fastmcp-server-middleware-error_handling.mdx
Normal file
40
docs/python-sdk/fastmcp-server-middleware-error_handling.mdx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
title: error_handling
|
||||
sidebarTitle: error_handling
|
||||
---
|
||||
|
||||
# `fastmcp.server.middleware.error_handling`
|
||||
|
||||
|
||||
Error handling middleware for consistent error responses and tracking.
|
||||
|
||||
## Classes
|
||||
|
||||
### `ErrorHandlingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/error_handling.py#L15"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Middleware that provides consistent error handling and logging.
|
||||
|
||||
Catches exceptions, logs them appropriately, and converts them to
|
||||
proper MCP error responses. Also tracks error patterns for monitoring.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get_error_stats` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/error_handling.py#L121"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_error_stats(self) -> dict[str, int]
|
||||
```
|
||||
|
||||
Get error statistics for monitoring.
|
||||
|
||||
|
||||
### `RetryMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/error_handling.py#L126"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Middleware that implements automatic retry logic for failed requests.
|
||||
|
||||
Retries requests that fail with transient errors, using exponential
|
||||
backoff to avoid overwhelming the server or external dependencies.
|
||||
|
||||
29
docs/python-sdk/fastmcp-server-middleware-logging.mdx
Normal file
29
docs/python-sdk/fastmcp-server-middleware-logging.mdx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: logging
|
||||
sidebarTitle: logging
|
||||
---
|
||||
|
||||
# `fastmcp.server.middleware.logging`
|
||||
|
||||
|
||||
Comprehensive logging middleware for FastMCP servers.
|
||||
|
||||
## Classes
|
||||
|
||||
### `LoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L10"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Middleware that provides comprehensive request and response logging.
|
||||
|
||||
Logs all MCP messages with configurable detail levels. Useful for debugging,
|
||||
monitoring, and understanding server usage patterns.
|
||||
|
||||
|
||||
### `StructuredLoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L87"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Middleware that provides structured JSON logging for better log analysis.
|
||||
|
||||
Outputs structured logs that are easier to parse and analyze with log
|
||||
aggregation tools like ELK stack, Splunk, or cloud logging services.
|
||||
|
||||
56
docs/python-sdk/fastmcp-server-middleware-middleware.mdx
Normal file
56
docs/python-sdk/fastmcp-server-middleware-middleware.mdx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: middleware
|
||||
sidebarTitle: middleware
|
||||
---
|
||||
|
||||
# `fastmcp.server.middleware.middleware`
|
||||
|
||||
## Functions
|
||||
|
||||
### `make_middleware_wrapper` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L106"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
make_middleware_wrapper(middleware: Middleware, call_next: CallNext[T, R]) -> CallNext[T, R]
|
||||
```
|
||||
|
||||
|
||||
Create a wrapper that applies a single middleware to a context. The
|
||||
closure bakes in the middleware and call_next function, so it can be
|
||||
passed to other functions that expect a call_next function.
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `CallNext` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L36"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
### `CallToolResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L56"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
### `ListToolsResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L62"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
### `ListResourcesResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L67"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
### `ListResourceTemplatesResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L72"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
### `ListPromptsResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L77"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
### `ServerResultProtocol` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L82"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
### `MiddlewareContext` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L87"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Unified context for all middleware operations.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `copy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L102"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
copy(self, **kwargs: Any) -> MiddlewareContext[T]
|
||||
```
|
||||
|
||||
### `Middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L119"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Base class for FastMCP middleware with dispatching hooks.
|
||||
|
||||
47
docs/python-sdk/fastmcp-server-middleware-rate_limiting.mdx
Normal file
47
docs/python-sdk/fastmcp-server-middleware-rate_limiting.mdx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: rate_limiting
|
||||
sidebarTitle: rate_limiting
|
||||
---
|
||||
|
||||
# `fastmcp.server.middleware.rate_limiting`
|
||||
|
||||
|
||||
Rate limiting middleware for protecting FastMCP servers from abuse.
|
||||
|
||||
## Classes
|
||||
|
||||
### `RateLimitError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L15"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Error raised when rate limit is exceeded.
|
||||
|
||||
|
||||
### `TokenBucketRateLimiter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L22"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Token bucket implementation for rate limiting.
|
||||
|
||||
|
||||
### `SlidingWindowRateLimiter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L61"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Sliding window rate limiter implementation.
|
||||
|
||||
|
||||
### `RateLimitingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L92"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Middleware that implements rate limiting to prevent server abuse.
|
||||
|
||||
Uses a token bucket algorithm by default, allowing for burst traffic
|
||||
while maintaining a sustainable long-term rate.
|
||||
|
||||
|
||||
### `SlidingWindowRateLimitingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/rate_limiting.py#L170"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Middleware that implements sliding window rate limiting.
|
||||
|
||||
Uses a sliding window approach which provides more precise rate limiting
|
||||
but uses more memory to track individual request timestamps.
|
||||
|
||||
29
docs/python-sdk/fastmcp-server-middleware-timing.mdx
Normal file
29
docs/python-sdk/fastmcp-server-middleware-timing.mdx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: timing
|
||||
sidebarTitle: timing
|
||||
---
|
||||
|
||||
# `fastmcp.server.middleware.timing`
|
||||
|
||||
|
||||
Timing middleware for measuring and logging request performance.
|
||||
|
||||
## Classes
|
||||
|
||||
### `TimingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/timing.py#L10"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Middleware that logs the execution time of requests.
|
||||
|
||||
Only measures and logs timing for request messages (not notifications).
|
||||
Provides insights into performance characteristics of your MCP server.
|
||||
|
||||
|
||||
### `DetailedTimingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/timing.py#L60"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Enhanced timing middleware with per-operation breakdowns.
|
||||
|
||||
Provides detailed timing information for different types of MCP operations,
|
||||
allowing you to identify performance bottlenecks in specific operations.
|
||||
|
||||
|
|
@ -10,13 +10,13 @@ FastMCP server implementation for OpenAPI integration.
|
|||
|
||||
## Classes
|
||||
|
||||
### `MCPType`
|
||||
### `MCPType` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L76"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Type of FastMCP component to create from a route.
|
||||
|
||||
|
||||
### `RouteType`
|
||||
### `RouteType` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L95"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Deprecated: Use MCPType instead.
|
||||
|
|
@ -24,31 +24,31 @@ Deprecated: Use MCPType instead.
|
|||
This enum is kept for backward compatibility and will be removed in a future version.
|
||||
|
||||
|
||||
### `RouteMap`
|
||||
### `RouteMap` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L109"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Mapping configuration for HTTP routes to FastMCP component types.
|
||||
|
||||
|
||||
### `OpenAPITool`
|
||||
### `OpenAPITool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L227"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Tool implementation for OpenAPI endpoints.
|
||||
|
||||
|
||||
### `OpenAPIResource`
|
||||
### `OpenAPIResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L478"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Resource implementation for OpenAPI endpoints.
|
||||
|
||||
|
||||
### `OpenAPIResourceTemplate`
|
||||
### `OpenAPIResourceTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L597"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Resource template implementation for OpenAPI endpoints.
|
||||
|
||||
|
||||
### `FastMCPOpenAPI`
|
||||
### `FastMCPOpenAPI` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L651"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
FastMCP server implementation that creates components from an OpenAPI schema.
|
||||
|
|
|
|||
|
|
@ -7,25 +7,25 @@ sidebarTitle: proxy
|
|||
|
||||
## Classes
|
||||
|
||||
### `ProxyToolManager`
|
||||
### `ProxyToolManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L36"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A ToolManager that sources its tools from a remote client in addition to local and mounted tools.
|
||||
|
||||
|
||||
### `ProxyResourceManager`
|
||||
### `ProxyResourceManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L81"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A ResourceManager that sources its resources from a remote client in addition to local and mounted resources.
|
||||
|
||||
|
||||
### `ProxyPromptManager`
|
||||
### `ProxyPromptManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L159"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts.
|
||||
|
||||
|
||||
### `ProxyTool`
|
||||
### `ProxyTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L209"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A Tool that represents and executes a tool on a remote server.
|
||||
|
|
@ -33,7 +33,7 @@ A Tool that represents and executes a tool on a remote server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_mcp_tool`
|
||||
#### `from_mcp_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L219"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
|
||||
|
|
@ -42,7 +42,7 @@ from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
|
|||
Factory method to create a ProxyTool from a raw MCP tool schema.
|
||||
|
||||
|
||||
### `ProxyResource`
|
||||
### `ProxyResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L246"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A Resource that represents and reads a resource from a remote server.
|
||||
|
|
@ -50,7 +50,7 @@ A Resource that represents and reads a resource from a remote server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_mcp_resource`
|
||||
#### `from_mcp_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L260"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> ProxyResource
|
||||
|
|
@ -59,7 +59,7 @@ from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> Prox
|
|||
Factory method to create a ProxyResource from a raw MCP resource schema.
|
||||
|
||||
|
||||
### `ProxyTemplate`
|
||||
### `ProxyTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L287"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A ResourceTemplate that represents and creates resources from a remote server template.
|
||||
|
|
@ -67,7 +67,7 @@ A ResourceTemplate that represents and creates resources from a remote server te
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_mcp_template`
|
||||
#### `from_mcp_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L297"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate) -> ProxyTemplate
|
||||
|
|
@ -76,7 +76,7 @@ from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate)
|
|||
Factory method to create a ProxyTemplate from a raw MCP template schema.
|
||||
|
||||
|
||||
### `ProxyPrompt`
|
||||
### `ProxyPrompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L343"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A Prompt that represents and renders a prompt from a remote server.
|
||||
|
|
@ -84,7 +84,7 @@ A Prompt that represents and renders a prompt from a remote server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_mcp_prompt`
|
||||
#### `from_mcp_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L355"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPrompt
|
||||
|
|
@ -93,7 +93,7 @@ from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPromp
|
|||
Factory method to create a ProxyPrompt from a raw MCP prompt schema.
|
||||
|
||||
|
||||
### `FastMCPProxy`
|
||||
### `FastMCPProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L381"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A FastMCP server that acts as a proxy to a remote MCP-compliant server.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ FastMCP - A more ergonomic interface for MCP servers.
|
|||
|
||||
## Functions
|
||||
|
||||
### `add_resource_prefix`
|
||||
### `add_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1879"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
|
||||
|
|
@ -19,26 +19,36 @@ add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'p
|
|||
|
||||
Add a prefix to a resource URI.
|
||||
|
||||
Args:
|
||||
uri: The original resource URI
|
||||
prefix: The prefix to add
|
||||
**Args:**
|
||||
- `uri`: The original resource URI
|
||||
- `prefix`: The prefix to add
|
||||
|
||||
Returns:
|
||||
The resource URI with the prefix added
|
||||
**Returns:**
|
||||
- The resource URI with the prefix added
|
||||
|
||||
Examples:
|
||||
>>> add_resource_prefix("resource://path/to/resource", "prefix")
|
||||
"resource://prefix/path/to/resource" # with new style
|
||||
>>> add_resource_prefix("resource://path/to/resource", "prefix")
|
||||
"prefix+resource://path/to/resource" # with legacy style
|
||||
>>> add_resource_prefix("resource:///absolute/path", "prefix")
|
||||
"resource://prefix//absolute/path" # with new style
|
||||
**Examples:**
|
||||
|
||||
Raises:
|
||||
ValueError: If the URI doesn't match the expected protocol://path format
|
||||
|
||||
With new style:
|
||||
```python
|
||||
add_resource_prefix("resource://path/to/resource", "prefix")
|
||||
"resource://prefix/path/to/resource"
|
||||
```
|
||||
With legacy style:
|
||||
```python
|
||||
add_resource_prefix("resource://path/to/resource", "prefix")
|
||||
"prefix+resource://path/to/resource"
|
||||
```
|
||||
With absolute path:
|
||||
```python
|
||||
add_resource_prefix("resource:///absolute/path", "prefix")
|
||||
"resource://prefix//absolute/path"
|
||||
```
|
||||
|
||||
### `remove_resource_prefix`
|
||||
**Raises:**
|
||||
- `ValueError`: If the URI doesn't match the expected protocol\://path format
|
||||
|
||||
|
||||
### `remove_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1939"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
|
||||
|
|
@ -47,26 +57,37 @@ remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol',
|
|||
|
||||
Remove a prefix from a resource URI.
|
||||
|
||||
Args:
|
||||
uri: The resource URI with a prefix
|
||||
prefix: The prefix to remove
|
||||
prefix_format: The format of the prefix to remove
|
||||
Returns:
|
||||
The resource URI with the prefix removed
|
||||
**Args:**
|
||||
- `uri`: The resource URI with a prefix
|
||||
- `prefix`: The prefix to remove
|
||||
- `prefix_format`: The format of the prefix to remove
|
||||
|
||||
Examples:
|
||||
>>> remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
|
||||
"resource://path/to/resource" # with new style
|
||||
>>> remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
|
||||
"resource://path/to/resource" # with legacy style
|
||||
>>> remove_resource_prefix("resource://prefix//absolute/path", "prefix")
|
||||
"resource:///absolute/path" # with new style
|
||||
Returns:
|
||||
The resource URI with the prefix removed
|
||||
|
||||
Raises:
|
||||
ValueError: If the URI doesn't match the expected protocol://path format
|
||||
|
||||
**Examples:**
|
||||
|
||||
### `has_resource_prefix`
|
||||
With new style:
|
||||
```python
|
||||
remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
|
||||
"resource://path/to/resource"
|
||||
```
|
||||
With legacy style:
|
||||
```python
|
||||
remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
|
||||
"resource://path/to/resource"
|
||||
```
|
||||
With absolute path:
|
||||
```python
|
||||
remove_resource_prefix("resource://prefix//absolute/path", "prefix")
|
||||
"resource:///absolute/path"
|
||||
```
|
||||
|
||||
**Raises:**
|
||||
- `ValueError`: If the URI doesn't match the expected protocol\://path format
|
||||
|
||||
|
||||
### `has_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2006"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> bool
|
||||
|
|
@ -75,53 +96,63 @@ has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'p
|
|||
|
||||
Check if a resource URI has a specific prefix.
|
||||
|
||||
Args:
|
||||
uri: The resource URI to check
|
||||
prefix: The prefix to look for
|
||||
**Args:**
|
||||
- `uri`: The resource URI to check
|
||||
- `prefix`: The prefix to look for
|
||||
|
||||
Returns:
|
||||
True if the URI has the specified prefix, False otherwise
|
||||
**Returns:**
|
||||
- True if the URI has the specified prefix, False otherwise
|
||||
|
||||
Examples:
|
||||
>>> has_resource_prefix("resource://prefix/path/to/resource", "prefix")
|
||||
True # with new style
|
||||
>>> has_resource_prefix("prefix+resource://path/to/resource", "prefix")
|
||||
True # with legacy style
|
||||
>>> has_resource_prefix("resource://other/path/to/resource", "prefix")
|
||||
False
|
||||
**Examples:**
|
||||
|
||||
With new style:
|
||||
```python
|
||||
has_resource_prefix("resource://prefix/path/to/resource", "prefix")
|
||||
True
|
||||
```
|
||||
With legacy style:
|
||||
```python
|
||||
has_resource_prefix("prefix+resource://path/to/resource", "prefix")
|
||||
True
|
||||
```
|
||||
With other path:
|
||||
```python
|
||||
has_resource_prefix("resource://other/path/to/resource", "prefix")
|
||||
False
|
||||
```
|
||||
|
||||
**Raises:**
|
||||
- `ValueError`: If the URI doesn't match the expected protocol\://path format
|
||||
|
||||
Raises:
|
||||
ValueError: If the URI doesn't match the expected protocol://path format
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `FastMCP`
|
||||
### `FastMCP` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L113"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `settings`
|
||||
#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L264"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
settings(self) -> Settings
|
||||
```
|
||||
|
||||
#### `name`
|
||||
#### `name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L275"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
name(self) -> str
|
||||
```
|
||||
|
||||
#### `instructions`
|
||||
#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L279"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
instructions(self) -> str | None
|
||||
```
|
||||
|
||||
#### `run`
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L304"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
run(self, transport: Literal['stdio', 'streamable-http', 'sse'] | None = None, **transport_kwargs: Any) -> None
|
||||
run(self, transport: Transport | None = None, **transport_kwargs: Any) -> None
|
||||
```
|
||||
|
||||
Run the FastMCP server. Note this is a synchronous function.
|
||||
|
|
@ -130,13 +161,13 @@ Run the FastMCP server. Note this is a synchronous function.
|
|||
- `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
|
||||
|
||||
|
||||
#### `add_middleware`
|
||||
#### `add_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L338"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_middleware(self, middleware: Middleware) -> None
|
||||
```
|
||||
|
||||
#### `custom_route`
|
||||
#### `custom_route` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L384"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True)
|
||||
|
|
@ -157,7 +188,7 @@ Starlette's reverse URL lookup feature)
|
|||
- `include_in_schema`: Whether to include in OpenAPI schema, defaults to True
|
||||
|
||||
|
||||
#### `add_tool`
|
||||
#### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L742"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_tool(self, tool: Tool) -> None
|
||||
|
|
@ -172,7 +203,7 @@ with the Context type annotation. See the @tool decorator for examples.
|
|||
- `tool`: The Tool instance to register
|
||||
|
||||
|
||||
#### `remove_tool`
|
||||
#### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L754"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_tool(self, name: str) -> None
|
||||
|
|
@ -187,19 +218,19 @@ Remove a tool from the server.
|
|||
- `NotFoundError`: If the tool is not found
|
||||
|
||||
|
||||
#### `tool`
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L767"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: AnyFunction) -> FunctionTool
|
||||
```
|
||||
|
||||
#### `tool`
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L780"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
|
||||
```
|
||||
|
||||
#### `tool`
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L792"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool
|
||||
|
|
@ -223,12 +254,37 @@ This decorator supports multiple calling patterns:
|
|||
- `name`: Optional name for the tool (keyword-only, alternative to name_or_fn)
|
||||
- `description`: Optional description of what the tool does
|
||||
- `tags`: Optional set of tags for categorizing the tool
|
||||
- `annotations`: Optional annotations about the tool's behavior (e.g. {"is_async"\: True})
|
||||
- `annotations`: Optional annotations about the tool's behavior
|
||||
- `exclude_args`: Optional list of argument names to exclude from the tool schema
|
||||
- `enabled`: Optional boolean to enable or disable the tool
|
||||
|
||||
**Examples:**
|
||||
|
||||
#### `add_resource`
|
||||
Register a tool with a custom name:
|
||||
```python
|
||||
@server.tool
|
||||
def my_tool(x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
# Register a tool with a custom name
|
||||
@server.tool
|
||||
def my_tool(x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
@server.tool("custom_name")
|
||||
def my_tool(x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
@server.tool(name="custom_name")
|
||||
def my_tool(x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
# Direct function call
|
||||
server.tool(my_function, name="custom_name")
|
||||
```
|
||||
|
||||
|
||||
#### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L912"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource(self, resource: Resource) -> None
|
||||
|
|
@ -240,7 +296,7 @@ Add a resource to the server.
|
|||
- `resource`: A Resource instance to add
|
||||
|
||||
|
||||
#### `add_template`
|
||||
#### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L922"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_template(self, template: ResourceTemplate) -> None
|
||||
|
|
@ -252,7 +308,7 @@ Add a resource template to the server.
|
|||
- `template`: A ResourceTemplate instance to add
|
||||
|
||||
|
||||
#### `add_resource_fn`
|
||||
#### `add_resource_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L930"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource_fn(self, fn: AnyFunction, uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> None
|
||||
|
|
@ -272,7 +328,7 @@ has parameters, it will be registered as a template resource.
|
|||
- `tags`: Optional set of tags for categorizing the resource
|
||||
|
||||
|
||||
#### `resource`
|
||||
#### `resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L969"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate]
|
||||
|
|
@ -301,8 +357,36 @@ has parameters, it will be registered as a template resource.
|
|||
- `tags`: Optional set of tags for categorizing the resource
|
||||
- `enabled`: Optional boolean to enable or disable the resource
|
||||
|
||||
**Examples:**
|
||||
|
||||
#### `add_prompt`
|
||||
Register a resource with a custom name:
|
||||
```python
|
||||
@server.resource("resource://my-resource")
|
||||
def get_data() -> str:
|
||||
return "Hello, world!"
|
||||
|
||||
@server.resource("resource://my-resource")
|
||||
async get_data() -> str:
|
||||
data = await fetch_data()
|
||||
return f"Hello, world! {data}"
|
||||
|
||||
@server.resource("resource://{city}/weather")
|
||||
def get_weather(city: str) -> str:
|
||||
return f"Weather for {city}"
|
||||
|
||||
@server.resource("resource://{city}/weather")
|
||||
def get_weather_with_context(city: str, ctx: Context) -> str:
|
||||
ctx.info(f"Fetching weather for {city}")
|
||||
return f"Weather for {city}"
|
||||
|
||||
@server.resource("resource://{city}/weather")
|
||||
async def get_weather(city: str) -> str:
|
||||
data = await fetch_weather(city)
|
||||
return f"Weather for {city}: {data}"
|
||||
```
|
||||
|
||||
|
||||
#### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1092"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_prompt(self, prompt: Prompt) -> None
|
||||
|
|
@ -314,19 +398,19 @@ Add a prompt to the server.
|
|||
- `prompt`: A Prompt instance to add
|
||||
|
||||
|
||||
#### `prompt`
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1102"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
|
||||
```
|
||||
|
||||
#### `prompt`
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1113"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
|
||||
```
|
||||
|
||||
#### `prompt`
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1123"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt
|
||||
|
|
@ -352,9 +436,11 @@ Decorator to register a prompt.
|
|||
tags: Optional set of tags for categorizing the prompt
|
||||
enabled: Optional boolean to enable or disable the prompt
|
||||
|
||||
Example:
|
||||
Examples:
|
||||
|
||||
```python
|
||||
@server.prompt
|
||||
def analyze_table(table_name: str) -> list\[Message]:
|
||||
def analyze_table(table_name: str) -> list[Message]:
|
||||
schema = read_table_schema(table_name)
|
||||
return [
|
||||
{
|
||||
|
|
@ -365,7 +451,7 @@ Decorator to register a prompt.
|
|||
]
|
||||
|
||||
@server.prompt()
|
||||
def analyze_with_context(table_name: str, ctx: Context) -> list\[Message]:
|
||||
def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
|
||||
ctx.info(f"Analyzing table {table_name}")
|
||||
schema = read_table_schema(table_name)
|
||||
return [
|
||||
|
|
@ -377,7 +463,7 @@ Decorator to register a prompt.
|
|||
]
|
||||
|
||||
@server.prompt("custom_name")
|
||||
def analyze_file(path: str) -> list\[Message]:
|
||||
def analyze_file(path: str) -> list[Message]:
|
||||
content = await read_file(path)
|
||||
return [
|
||||
{
|
||||
|
|
@ -393,14 +479,15 @@ Decorator to register a prompt.
|
|||
]
|
||||
|
||||
@server.prompt(name="custom_name")
|
||||
def another_prompt(data: str) -> list\[Message]:
|
||||
def another_prompt(data: str) -> list[Message]:
|
||||
return [{"role": "user", "content": data}]
|
||||
|
||||
# Direct function call
|
||||
server.prompt(my_function, name="custom_name")
|
||||
```
|
||||
|
||||
|
||||
#### `sse_app`
|
||||
#### `sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1344"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
sse_app(self, path: str | None = None, message_path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -414,7 +501,7 @@ Create a Starlette app for the SSE server.
|
|||
- `middleware`: A list of middleware to apply to the app
|
||||
|
||||
|
||||
#### `streamable_http_app`
|
||||
#### `streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1375"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
streamable_http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -427,10 +514,10 @@ Create a Starlette app for the StreamableHTTP server.
|
|||
- `middleware`: A list of middleware to apply to the app
|
||||
|
||||
|
||||
#### `http_app`
|
||||
#### `http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1396"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['streamable-http', 'sse'] = 'streamable-http') -> StarletteWithLifespan
|
||||
http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http') -> StarletteWithLifespan
|
||||
```
|
||||
|
||||
Create a Starlette app using the specified HTTP transport.
|
||||
|
|
@ -444,7 +531,7 @@ Create a Starlette app using the specified HTTP transport.
|
|||
- A Starlette application configured with the specified transport
|
||||
|
||||
|
||||
#### `mount`
|
||||
#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1470"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
mount(self, server: FastMCP[LifespanResultT], prefix: str | None = None, as_proxy: bool | None = None) -> None
|
||||
|
|
@ -498,7 +585,7 @@ automatically determined based on whether the server has a custom lifespan
|
|||
- `prompt_separator`: Deprecated. Separator character for prompt names.
|
||||
|
||||
|
||||
#### `from_openapi`
|
||||
#### `from_openapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1720"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI
|
||||
|
|
@ -507,7 +594,7 @@ from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route
|
|||
Create a FastMCP server from an OpenAPI specification.
|
||||
|
||||
|
||||
#### `from_fastapi`
|
||||
#### `from_fastapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1748"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI
|
||||
|
|
@ -516,7 +603,7 @@ from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap]
|
|||
Create a FastMCP server from a FastAPI application.
|
||||
|
||||
|
||||
#### `as_proxy`
|
||||
#### `as_proxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1790"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
|
||||
|
|
@ -524,13 +611,13 @@ as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any]
|
|||
|
||||
Create a FastMCP proxy server for the given backend.
|
||||
|
||||
The ``backend`` argument can be either an existing :class:`~fastmcp.client.Client`
|
||||
instance or any value accepted as the ``transport`` argument of
|
||||
:class:`~fastmcp.client.Client`. This mirrors the convenience of the
|
||||
``Client`` constructor.
|
||||
The `backend` argument can be either an existing `fastmcp.client.Client`
|
||||
instance or any value accepted as the `transport` argument of
|
||||
`fastmcp.client.Client`. This mirrors the convenience of the
|
||||
`fastmcp.client.Client` constructor.
|
||||
|
||||
|
||||
#### `from_client`
|
||||
#### `from_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1820"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy
|
||||
|
|
@ -539,4 +626,4 @@ from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPPr
|
|||
Create a FastMCP proxy server from a FastMCP client.
|
||||
|
||||
|
||||
### `MountedServer`
|
||||
### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1873"><Icon icon="github" size="14" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: settings
|
|||
|
||||
## Classes
|
||||
|
||||
### `ExtendedEnvSettingsSource`
|
||||
### `ExtendedEnvSettingsSource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L26"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A special EnvSettingsSource that allows for multiple env var prefixes to be used.
|
||||
|
|
@ -17,15 +17,15 @@ Raises a deprecation warning if the old `FASTMCP_SERVER_` prefix is used.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_field_value`
|
||||
#### `get_field_value` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L33"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]
|
||||
```
|
||||
|
||||
### `ExtendedSettingsConfigDict`
|
||||
### `ExtendedSettingsConfigDict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L53"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
### `Settings`
|
||||
### `Settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L57"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
FastMCP settings.
|
||||
|
|
@ -33,13 +33,13 @@ FastMCP settings.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `settings_customise_sources`
|
||||
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L69"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
settings_customise_sources(cls, settings_cls: type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource) -> tuple[PydanticBaseSettingsSource, ...]
|
||||
```
|
||||
|
||||
#### `settings`
|
||||
#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L87"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
settings(self) -> Self
|
||||
|
|
@ -49,7 +49,7 @@ This property is for backwards compatibility with FastMCP < 2.8.0,
|
|||
which accessed fastmcp.settings.settings
|
||||
|
||||
|
||||
#### `setup_logging`
|
||||
#### `setup_logging` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L182"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
setup_logging(self) -> Self
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: tool
|
|||
|
||||
## Functions
|
||||
|
||||
### `default_serializer`
|
||||
### `default_serializer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L34"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
default_serializer(data: Any) -> str
|
||||
|
|
@ -15,7 +15,7 @@ default_serializer(data: Any) -> str
|
|||
|
||||
## Classes
|
||||
|
||||
### `Tool`
|
||||
### `Tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L38"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Internal tool registration info.
|
||||
|
|
@ -23,13 +23,13 @@ Internal tool registration info.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_mcp_tool`
|
||||
#### `to_mcp_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L49"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_mcp_tool(self, **overrides: Any) -> MCPTool
|
||||
```
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L59"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> FunctionTool
|
||||
|
|
@ -38,17 +38,17 @@ from_function(fn: Callable[..., Any], name: str | None = None, description: str
|
|||
Create a Tool from a function.
|
||||
|
||||
|
||||
#### `from_tool`
|
||||
#### `from_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L86"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_tool(cls, tool: Tool, transform_fn: Callable[..., Any] | None = None, name: str | None = None, transform_args: dict[str, ArgTransform] | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> TransformedTool
|
||||
```
|
||||
|
||||
### `FunctionTool`
|
||||
### `FunctionTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L113"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L117"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> FunctionTool
|
||||
|
|
@ -57,11 +57,11 @@ from_function(cls, fn: Callable[..., Any], name: str | None = None, description:
|
|||
Create a Tool from a function.
|
||||
|
||||
|
||||
### `ParsedFunction`
|
||||
### `ParsedFunction` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L194"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function`
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L201"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[..., Any], exclude_args: list[str] | None = None, validate: bool = True) -> ParsedFunction
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: tool_manager
|
|||
|
||||
## Classes
|
||||
|
||||
### `ToolManager`
|
||||
### `ToolManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L22"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Manages FastMCP tools.
|
||||
|
|
@ -15,7 +15,7 @@ Manages FastMCP tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `mount`
|
||||
#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L46"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
mount(self, server: MountedServer) -> None
|
||||
|
|
@ -24,7 +24,7 @@ mount(self, server: MountedServer) -> None
|
|||
Adds a mounted server as a source for tools.
|
||||
|
||||
|
||||
#### `add_tool_from_fn`
|
||||
#### `add_tool_from_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L113"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_tool_from_fn(self, fn: Callable[..., Any], name: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, exclude_args: list[str] | None = None) -> Tool
|
||||
|
|
@ -33,7 +33,7 @@ add_tool_from_fn(self, fn: Callable[..., Any], name: str | None = None, descript
|
|||
Add a tool to the server.
|
||||
|
||||
|
||||
#### `add_tool`
|
||||
#### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L142"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
add_tool(self, tool: Tool) -> Tool
|
||||
|
|
@ -42,7 +42,7 @@ add_tool(self, tool: Tool) -> Tool
|
|||
Register a tool with the server.
|
||||
|
||||
|
||||
#### `remove_tool`
|
||||
#### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_manager.py#L159"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_tool(self, key: str) -> None
|
||||
|
|
|
|||
|
|
@ -7,58 +7,69 @@ sidebarTitle: tool_transform
|
|||
|
||||
## Classes
|
||||
|
||||
### `ArgTransform`
|
||||
### `ArgTransform` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L85"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Configuration for transforming a parent tool's argument.
|
||||
|
||||
This class allows fine-grained control over how individual arguments are transformed
|
||||
when creating a new tool from an existing one. You can rename arguments, change their
|
||||
descriptions, add default values, or hide them from clients while passing constants.
|
||||
This class allows fine-grained control over how individual arguments are transformed
|
||||
when creating a new tool from an existing one. You can rename arguments, change their
|
||||
descriptions, add default values, or hide them from clients while passing constants.
|
||||
|
||||
Attributes:
|
||||
name: New name for the argument. Use None to keep original name, or ... for no change.
|
||||
description: New description for the argument. Use None to remove description, or ... for no change.
|
||||
default: New default value for the argument. Use ... for no change.
|
||||
default_factory: Callable that returns a default value. Cannot be used with default.
|
||||
type: New type for the argument. Use ... for no change.
|
||||
hide: If True, hide this argument from clients but pass a constant value to parent.
|
||||
required: If True, make argument required (remove default). Use ... for no change.
|
||||
examples: Examples for the argument. Use ... for no change.
|
||||
**Examples:**
|
||||
|
||||
Examples:
|
||||
# Rename argument 'old_name' to 'new_name'
|
||||
ArgTransform(name="new_name")
|
||||
Rename argument 'old_name' to 'new_name'
|
||||
```python
|
||||
ArgTransform(name="new_name")
|
||||
```
|
||||
|
||||
# Change description only
|
||||
ArgTransform(description="Updated description")
|
||||
Change description only
|
||||
```python
|
||||
ArgTransform(description="Updated description")
|
||||
```
|
||||
|
||||
# Add a default value (makes argument optional)
|
||||
ArgTransform(default=42)
|
||||
Add a default value (makes argument optional)
|
||||
```python
|
||||
ArgTransform(default=42)
|
||||
```
|
||||
|
||||
# Add a default factory (makes argument optional)
|
||||
ArgTransform(default_factory=lambda: time.time())
|
||||
Add a default factory (makes argument optional)
|
||||
```python
|
||||
ArgTransform(default_factory=lambda: time.time())
|
||||
```
|
||||
|
||||
# Change the type
|
||||
ArgTransform(type=str)
|
||||
Change the type
|
||||
```python
|
||||
ArgTransform(type=str)
|
||||
```
|
||||
|
||||
# Hide the argument entirely from clients
|
||||
ArgTransform(hide=True)
|
||||
Hide the argument entirely from clients
|
||||
```python
|
||||
ArgTransform(hide=True)
|
||||
```
|
||||
|
||||
# Hide argument but pass a constant value to parent
|
||||
ArgTransform(hide=True, default="constant_value")
|
||||
Hide argument but pass a constant value to parent
|
||||
```python
|
||||
ArgTransform(hide=True, default="constant_value")
|
||||
```
|
||||
|
||||
# Hide argument but pass a factory-generated value to parent
|
||||
ArgTransform(hide=True, default_factory=lambda: uuid.uuid4().hex)
|
||||
Hide argument but pass a factory-generated value to parent
|
||||
```python
|
||||
ArgTransform(hide=True, default_factory=lambda: uuid.uuid4().hex)
|
||||
```
|
||||
|
||||
# Make an optional parameter required (removes any default)
|
||||
ArgTransform(required=True)
|
||||
Make an optional parameter required (removes any default)
|
||||
```python
|
||||
ArgTransform(required=True)
|
||||
```
|
||||
|
||||
# Combine multiple transformations
|
||||
ArgTransform(name="new_name", description="New desc", default=None, type=int)
|
||||
|
||||
Combine multiple transformations
|
||||
```python
|
||||
ArgTransform(name="new_name", description="New desc", default=None, type=int)
|
||||
```
|
||||
|
||||
### `TransformedTool`
|
||||
|
||||
### `TransformedTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L199"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
A tool that is transformed from another tool.
|
||||
|
|
@ -74,7 +85,7 @@ with transformed arguments.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_tool`
|
||||
#### `from_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L280"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_tool(cls, tool: Tool, name: str | None = None, description: str | None = None, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> TransformedTool
|
||||
|
|
@ -90,9 +101,9 @@ argument names.
|
|||
- `name`: New name for the tool. Defaults to parent tool's name.
|
||||
- `transform_args`: Optional transformations for parent tool arguments.
|
||||
Only specified arguments are transformed, others pass through unchanged\:
|
||||
- str\: Simple rename
|
||||
- ArgTransform\: Complex transformation (rename/description/default/drop)
|
||||
- None\: Drop the argument
|
||||
- Simple rename (str)
|
||||
- Complex transformation (rename/description/default/drop) (ArgTransform)
|
||||
- Drop the argument (None)
|
||||
- `description`: New description. Defaults to parent's description.
|
||||
- `tags`: New tags. Defaults to parent's tags.
|
||||
- `annotations`: New annotations. Defaults to parent's annotations.
|
||||
|
|
@ -101,17 +112,28 @@ Only specified arguments are transformed, others pass through unchanged\:
|
|||
**Returns:**
|
||||
- TransformedTool with the specified transformations.
|
||||
|
||||
Examples:
|
||||
- # Transform specific arguments only
|
||||
- Tool.from_tool(parent, transform_args={"old": "new"}) # Others unchanged
|
||||
- # Custom function with partial transforms
|
||||
- async def custom(x: int, y: int) -> str:
|
||||
result = await forward(x=x, y=y)
|
||||
return f"Custom: {result}"
|
||||
- Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"})
|
||||
- # Using **kwargs (gets all args, transformed and untransformed)
|
||||
- async def flexible(**kwargs) -> str:
|
||||
result = await forward(**kwargs)
|
||||
return f"Got: {kwargs}"
|
||||
- Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"})
|
||||
**Examples:**
|
||||
|
||||
# Transform specific arguments only
|
||||
```python
|
||||
Tool.from_tool(parent, transform_args={"old": "new"}) # Others unchanged
|
||||
```
|
||||
|
||||
# Custom function with partial transforms
|
||||
```python
|
||||
async def custom(x: int, y: int) -> str:
|
||||
result = await forward(x=x, y=y)
|
||||
return f"Custom: {result}"
|
||||
|
||||
Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"})
|
||||
```
|
||||
|
||||
# Using **kwargs (gets all args, transformed and untransformed)
|
||||
```python
|
||||
async def flexible(**kwargs) -> str:
|
||||
result = await forward(**kwargs)
|
||||
return f"Got: {kwargs}"
|
||||
|
||||
Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"})
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -7,23 +7,23 @@ sidebarTitle: cache
|
|||
|
||||
## Classes
|
||||
|
||||
### `TimedCache`
|
||||
### `TimedCache` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cache.py#L7"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `set`
|
||||
#### `set` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cache.py#L14"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
set(self, key: Any, value: Any) -> None
|
||||
```
|
||||
|
||||
#### `get`
|
||||
#### `get` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cache.py#L18"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get(self, key: Any) -> Any
|
||||
```
|
||||
|
||||
#### `clear`
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cache.py#L25"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
clear(self) -> None
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: components
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCPComponent`
|
||||
### `FastMCPComponent` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L21"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Base class for FastMCP tools, prompts, resources, and resource templates.
|
||||
|
|
@ -15,7 +15,7 @@ Base class for FastMCP tools, prompts, resources, and resource templates.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `key`
|
||||
#### `key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L48"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
key(self) -> str
|
||||
|
|
@ -27,13 +27,13 @@ keys having a certain value, as the same tool loaded from different
|
|||
hierarchies of servers may have different keys.
|
||||
|
||||
|
||||
#### `with_key`
|
||||
#### `with_key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L57"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
with_key(self, key: str) -> Self
|
||||
```
|
||||
|
||||
#### `enable`
|
||||
#### `enable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L69"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
enable(self) -> None
|
||||
|
|
@ -42,7 +42,7 @@ enable(self) -> None
|
|||
Enable the component.
|
||||
|
||||
|
||||
#### `disable`
|
||||
#### `disable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/components.py#L73"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
disable(self) -> None
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ sidebarTitle: exceptions
|
|||
|
||||
## Functions
|
||||
|
||||
### `iter_exc`
|
||||
### `iter_exc` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/exceptions.py#L12"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
iter_exc(group: BaseExceptionGroup)
|
||||
```
|
||||
|
||||
### `get_catch_handlers`
|
||||
### `get_catch_handlers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/exceptions.py#L42"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_catch_handlers() -> Mapping[type[BaseException] | Iterable[type[BaseException]], Callable[[BaseExceptionGroup[Any]], Any]]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: http
|
|||
|
||||
## Functions
|
||||
|
||||
### `find_available_port`
|
||||
### `find_available_port` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/http.py#L4"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
find_available_port() -> int
|
||||
|
|
|
|||
41
docs/python-sdk/fastmcp-utilities-inspect.mdx
Normal file
41
docs/python-sdk/fastmcp-utilities-inspect.mdx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: inspect
|
||||
sidebarTitle: inspect
|
||||
---
|
||||
|
||||
# `fastmcp.utilities.inspect`
|
||||
|
||||
|
||||
Utilities for inspecting FastMCP instances.
|
||||
|
||||
## Classes
|
||||
|
||||
### `ToolInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L16"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Information about a tool.
|
||||
|
||||
|
||||
### `PromptInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L29"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Information about a prompt.
|
||||
|
||||
|
||||
### `ResourceInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L41"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Information about a resource.
|
||||
|
||||
|
||||
### `TemplateInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L54"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Information about a resource template.
|
||||
|
||||
|
||||
### `FastMCPInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L67"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Information extracted from a FastMCP instance.
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ sidebarTitle: json_schema
|
|||
|
||||
## Functions
|
||||
|
||||
### `compress_schema`
|
||||
### `compress_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/json_schema.py#L130"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
compress_schema(schema: dict, prune_params: list[str] | None = None, prune_defs: bool = True, prune_additional_properties: bool = True, prune_titles: bool = False) -> dict
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Logging utilities for FastMCP.
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_logger`
|
||||
### `get_logger` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L10"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_logger(name: str) -> logging.Logger
|
||||
|
|
@ -26,7 +26,7 @@ Get a logger nested under FastMCP namespace.
|
|||
- a configured logger instance
|
||||
|
||||
|
||||
### `configure_logging`
|
||||
### `configure_logging` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L22"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
configure_logging(level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] | int = 'INFO', logger: logging.Logger | None = None, enable_rich_tracebacks: bool = True) -> None
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ sidebarTitle: mcp_config
|
|||
|
||||
## Functions
|
||||
|
||||
### `infer_transport_type_from_url`
|
||||
### `infer_transport_type_from_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L20"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
infer_transport_type_from_url(url: str | AnyUrl) -> Literal['streamable-http', 'sse']
|
||||
infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -19,31 +19,31 @@ Infer the appropriate transport type from the given URL.
|
|||
|
||||
## Classes
|
||||
|
||||
### `StdioMCPServer`
|
||||
### `StdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L40"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `to_transport`
|
||||
#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L47"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_transport(self) -> StdioTransport
|
||||
```
|
||||
|
||||
### `RemoteMCPServer`
|
||||
### `RemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L58"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `to_transport`
|
||||
#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L71"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_transport(self) -> StreamableHttpTransport | SSETransport
|
||||
```
|
||||
|
||||
### `MCPConfig`
|
||||
### `MCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L88"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `from_dict`
|
||||
#### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L92"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
from_dict(cls, config: dict[str, Any]) -> MCPConfig
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: openapi
|
|||
|
||||
## Functions
|
||||
|
||||
### `parse_openapi_to_http_routes`
|
||||
### `parse_openapi_to_http_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L112"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
parse_openapi_to_http_routes(openapi_dict: dict[str, Any]) -> list[HTTPRoute]
|
||||
|
|
@ -20,7 +20,7 @@ using the openapi-pydantic library.
|
|||
Supports both OpenAPI 3.0.x and 3.1.x versions.
|
||||
|
||||
|
||||
### `clean_schema_for_display`
|
||||
### `clean_schema_for_display` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L570"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
clean_schema_for_display(schema: JsonSchema | None) -> JsonSchema | None
|
||||
|
|
@ -30,7 +30,7 @@ clean_schema_for_display(schema: JsonSchema | None) -> JsonSchema | None
|
|||
Clean up a schema dictionary for display by removing internal/complex fields.
|
||||
|
||||
|
||||
### `generate_example_from_schema`
|
||||
### `generate_example_from_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L630"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_example_from_schema(schema: JsonSchema | None) -> Any
|
||||
|
|
@ -41,7 +41,7 @@ Generate a simple example value from a JSON schema dictionary.
|
|||
Very basic implementation focusing on types.
|
||||
|
||||
|
||||
### `format_json_for_description`
|
||||
### `format_json_for_description` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L713"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
format_json_for_description(data: Any, indent: int = 2) -> str
|
||||
|
|
@ -51,7 +51,7 @@ format_json_for_description(data: Any, indent: int = 2) -> str
|
|||
Formats Python data as a JSON string block for markdown.
|
||||
|
||||
|
||||
### `format_description_with_responses`
|
||||
### `format_description_with_responses` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L722"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
format_description_with_responses(base_description: str, responses: dict[str, Any], parameters: list[ParameterInfo] | None = None, request_body: RequestBodyInfo | None = None) -> str
|
||||
|
|
@ -76,31 +76,31 @@ including its description, whether it is required, and its content schema.
|
|||
|
||||
## Classes
|
||||
|
||||
### `ParameterInfo`
|
||||
### `ParameterInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L42"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Represents a single parameter for an HTTP operation in our IR.
|
||||
|
||||
|
||||
### `RequestBodyInfo`
|
||||
### `RequestBodyInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L52"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Represents the request body for an HTTP operation in our IR.
|
||||
|
||||
|
||||
### `ResponseInfo`
|
||||
### `ResponseInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L62"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Represents response information in our IR.
|
||||
|
||||
|
||||
### `HTTPRoute`
|
||||
### `HTTPRoute` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L70"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Intermediate Representation for a single OpenAPI operation.
|
||||
|
||||
|
||||
### `OpenAPIParser`
|
||||
### `OpenAPIParser` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L164"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Unified parser for OpenAPI schemas with generic type parameters to handle both 3.0 and 3.1.
|
||||
|
|
@ -108,7 +108,7 @@ Unified parser for OpenAPI schemas with generic type parameters to handle both 3
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `parse`
|
||||
#### `parse` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/openapi.py#L469"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
parse(self) -> list[HTTPRoute]
|
||||
|
|
|
|||
42
docs/python-sdk/fastmcp-utilities-tests.mdx
Normal file
42
docs/python-sdk/fastmcp-utilities-tests.mdx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: tests
|
||||
sidebarTitle: tests
|
||||
---
|
||||
|
||||
# `fastmcp.utilities.tests`
|
||||
|
||||
## Functions
|
||||
|
||||
### `temporary_settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L21"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
temporary_settings(**kwargs: Any)
|
||||
```
|
||||
|
||||
|
||||
Temporarily override FastMCP setting values.
|
||||
|
||||
**Args:**
|
||||
- `**kwargs`: The settings to override, including nested settings.
|
||||
|
||||
|
||||
### `run_server_in_process` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L74"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
run_server_in_process(server_fn: Callable[..., None], *args, **kwargs) -> Generator[str, None, None]
|
||||
```
|
||||
|
||||
|
||||
Context manager that runs a FastMCP server in a separate process and
|
||||
returns the server URL. When the context manager is exited, the server process is killed.
|
||||
|
||||
**Args:**
|
||||
- `server_fn`: The function that runs a FastMCP server. FastMCP servers are
|
||||
not pickleable, so we need a function that creates and runs one.
|
||||
- `*args`: Arguments to pass to the server function.
|
||||
- `provide_host_and_port`: Whether to provide the host and port to the server function as kwargs.
|
||||
- `**kwargs`: Keyword arguments to pass to the server function.
|
||||
|
||||
**Returns:**
|
||||
- The server URL.
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ Common types used across FastMCP.
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_cached_typeadapter`
|
||||
### `get_cached_typeadapter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L35"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
get_cached_typeadapter(cls: T) -> TypeAdapter[T]
|
||||
|
|
@ -23,7 +23,7 @@ However, this isn't feasible for user-generated functions. Instead, we use a
|
|||
cache to minimize the cost of creating them as much as possible.
|
||||
|
||||
|
||||
### `issubclass_safe`
|
||||
### `issubclass_safe` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L45"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
issubclass_safe(cls: type, base: type) -> bool
|
||||
|
|
@ -33,7 +33,7 @@ issubclass_safe(cls: type, base: type) -> bool
|
|||
Check if cls is a subclass of base, even if cls is a type variable.
|
||||
|
||||
|
||||
### `is_class_member_of_type`
|
||||
### `is_class_member_of_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L55"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
is_class_member_of_type(cls: type, base: type) -> bool
|
||||
|
|
@ -46,7 +46,7 @@ Base can be a type, a UnionType, or an Annotated type. Generic types are not
|
|||
considered members (e.g. T is not a member of list\[T]).
|
||||
|
||||
|
||||
### `find_kwarg_by_type`
|
||||
### `find_kwarg_by_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L77"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
find_kwarg_by_type(fn: Callable, kwarg_type: type) -> str | None
|
||||
|
|
@ -60,13 +60,13 @@ Includes union types that contain the kwarg_type, as well as Annotated types.
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCPBaseModel`
|
||||
### `FastMCPBaseModel` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L28"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Base model for FastMCP models.
|
||||
|
||||
|
||||
### `Image`
|
||||
### `Image` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L94"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Helper class for returning images from tools.
|
||||
|
|
@ -74,7 +74,7 @@ Helper class for returning images from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_image_content`
|
||||
#### `to_image_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L131"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_image_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> ImageContent
|
||||
|
|
@ -83,7 +83,7 @@ to_image_content(self, mime_type: str | None = None, annotations: Annotations |
|
|||
Convert to MCP ImageContent.
|
||||
|
||||
|
||||
### `Audio`
|
||||
### `Audio` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L153"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Helper class for returning audio from tools.
|
||||
|
|
@ -91,13 +91,13 @@ Helper class for returning audio from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_audio_content`
|
||||
#### `to_audio_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L190"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_audio_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> AudioContent
|
||||
```
|
||||
|
||||
### `File`
|
||||
### `File` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L211"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
|
||||
Helper class for returning audio from tools.
|
||||
|
|
@ -105,7 +105,7 @@ Helper class for returning audio from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_resource_content`
|
||||
#### `to_resource_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L250"><Icon icon="github" size="14" /></a></sup>
|
||||
|
||||
```python
|
||||
to_resource_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> EmbeddedResource
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ The [MCP specification](https://modelcontextprotocol.io/specification/2025-03-26
|
|||
|
||||
Bearer Token authentication is a common way to secure HTTP-based APIs. In this model, the client sends a token (usually a JSON Web Token or JWT) in the `Authorization` header with the "Bearer" scheme. The server then validates this token to grant or deny access.
|
||||
|
||||
FastMCP supports Bearer Token authentication for its HTTP-based transports (`streamable-http` and `sse`), allowing you to protect your server from unauthorized access.
|
||||
FastMCP supports Bearer Token authentication for its HTTP-based transports (`http` and `sse`), allowing you to protect your server from unauthorized access.
|
||||
|
||||
## Authentication Strategy
|
||||
|
||||
|
|
@ -61,13 +61,27 @@ mcp = FastMCP(name="My MCP Server", auth=auth)
|
|||
|
||||
### Configuration Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `public_key` | `str` | If `jwks_uri` is not provided | RSA public key in PEM format for static key validation |
|
||||
| `jwks_uri` | `str` | If `public_key` is not provided | URL for JSON Web Key Set endpoint |
|
||||
| `issuer` | `str` | No | Expected JWT `iss` claim value |
|
||||
| `audience` | `str` | No | Expected JWT `aud` claim value |
|
||||
| `required_scopes` | `list[str]` | No | Global scopes required for all requests |
|
||||
<Card icon="code" title="BearerAuthProvider Configuration">
|
||||
<ParamField body="public_key" type="str">
|
||||
RSA public key in PEM format for static key validation. Required if `jwks_uri` is not provided
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="jwks_uri" type="str">
|
||||
URL for JSON Web Key Set endpoint. Required if `public_key` is not provided
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="issuer" type="str | None">
|
||||
Expected JWT `iss` claim value
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="audience" type="str | None">
|
||||
Expected JWT `aud` claim value
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="required_scopes" type="list[str] | None">
|
||||
Global scopes required for all requests
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
#### Public Key
|
||||
|
||||
|
|
@ -141,15 +155,35 @@ print(f"Test token: {token}")
|
|||
|
||||
The `create_token()` method accepts these parameters:
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `subject` | `str` | `"fastmcp-user"` | JWT subject claim (usually user ID) |
|
||||
| `issuer` | `str` | `"https://fastmcp.example.com"` | JWT issuer claim |
|
||||
| `audience` | `str` | `None` | JWT audience claim |
|
||||
| `scopes` | `list[str]` | `None` | OAuth scopes to include |
|
||||
| `expires_in_seconds` | `int` | `3600` | Token expiration time |
|
||||
| `additional_claims` | `dict` | `None` | Extra claims to include |
|
||||
| `kid` | `str` | `None` | Key ID for JWKS lookup |
|
||||
<Card icon="code" title="create_token() Parameters">
|
||||
<ParamField body="subject" type="str" default="fastmcp-user">
|
||||
JWT subject claim (usually user ID)
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="issuer" type="str" default="https://fastmcp.example.com">
|
||||
JWT issuer claim
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="audience" type="str | None">
|
||||
JWT audience claim
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="scopes" type="list[str] | None">
|
||||
OAuth scopes to include
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="expires_in_seconds" type="int" default="3600">
|
||||
Token expiration time in seconds
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="additional_claims" type="dict | None">
|
||||
Extra claims to include in the token
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="kid" type="str | None">
|
||||
Key ID for JWKS lookup
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
|
||||
## Accessing Token Claims
|
||||
|
|
@ -179,10 +213,21 @@ async def get_my_data(ctx: Context) -> dict:
|
|||
|
||||
### AccessToken Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `token` | `str` | The raw JWT string |
|
||||
| `client_id` | `str` | Authenticated principal identifier |
|
||||
| `scopes` | `list[str]` | Granted scopes |
|
||||
| `expires_at` | `datetime \| None` | Token expiration timestamp |
|
||||
<Card icon="code" title="AccessToken Properties">
|
||||
<ParamField body="token" type="str">
|
||||
The raw JWT string
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="client_id" type="str">
|
||||
Authenticated principal identifier
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="scopes" type="list[str]">
|
||||
Granted scopes
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="expires_at" type="datetime | None">
|
||||
Token expiration timestamp
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -275,6 +275,25 @@ async def generate_example(concept: str, ctx: Context) -> str:
|
|||
|
||||
See [Client Sampling](/clients/client#llm-sampling) for more details on how clients handle these requests.
|
||||
|
||||
### Component Changes
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
FastMCP automatically sends list change notifications when components (such as tools, resources, or prompts) are added, removed, enabled, or disabled. In rare cases where you need to manually trigger these notifications, you can use the context methods:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def custom_tool_management(ctx: Context) -> str:
|
||||
"""Example of manual notification after custom tool changes."""
|
||||
# After making custom changes to tools
|
||||
await ctx.send_tool_list_changed()
|
||||
await ctx.send_resource_list_changed()
|
||||
await ctx.send_prompt_list_changed()
|
||||
return "Notifications sent"
|
||||
```
|
||||
|
||||
These methods are primarily used internally by FastMCP's automatic notification system and most users will not need to invoke them directly.
|
||||
|
||||
### Request Information
|
||||
|
||||
Access metadata about the current request and client.
|
||||
|
|
|
|||
|
|
@ -78,10 +78,13 @@ When a request comes in, **multiple hooks may be called for the same request**,
|
|||
2. **`on_request` or `on_notification`** - Called based on the message type
|
||||
3. **Operation-specific hooks** - Called for specific MCP operations like `on_call_tool`
|
||||
|
||||
For example, when a client calls a tool, your middleware will receive **three separate hook calls**:
|
||||
1. First: `on_message` (because it's any MCP message)
|
||||
2. Second: `on_request` (because tool calls expect responses)
|
||||
3. Third: `on_call_tool` (because it's specifically a tool execution)
|
||||
For example, when a client calls a tool, your middleware will receive **multiple hook calls**:
|
||||
1. `on_message` and `on_request` for any initial tool discovery operations (list_tools)
|
||||
2. `on_message` (because it's any MCP message) for the tool call itself
|
||||
3. `on_request` (because tool calls expect responses) for the tool call itself
|
||||
4. `on_call_tool` (because it's specifically a tool execution) for the tool call itself
|
||||
|
||||
Note that the MCP SDK may perform additional operations like listing tools for caching purposes, which will trigger additional middleware calls beyond just the direct tool execution.
|
||||
|
||||
This hierarchy allows you to target your middleware logic with the right level of specificity. Use `on_message` for broad concerns like logging, `on_request` for authentication, and `on_call_tool` for tool-specific logic like performance monitoring.
|
||||
|
||||
|
|
@ -329,93 +332,246 @@ parent.mount(child, prefix="child")
|
|||
|
||||
When a client calls "child_tool", the request will flow through the parent's authentication middleware first, then route to the child server where it will go through the child's logging middleware.
|
||||
|
||||
## Examples
|
||||
## Built-in Middleware Examples
|
||||
|
||||
### Authentication Middleware
|
||||
FastMCP includes several middleware implementations that demonstrate best practices and provide immediately useful functionality. Let's explore how each type works by building simplified versions, then see how to use the full implementations.
|
||||
|
||||
This middleware checks for a valid authorization token on all requests:
|
||||
### Timing Middleware
|
||||
|
||||
```python
|
||||
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
||||
from fastmcp.exceptions import ToolError
|
||||
Performance monitoring is essential for understanding your server's behavior and identifying bottlenecks. FastMCP includes timing middleware at `fastmcp.server.middleware.timing`.
|
||||
|
||||
class AuthenticationMiddleware(Middleware):
|
||||
def __init__(self, required_token: str):
|
||||
self.required_token = required_token
|
||||
|
||||
async def on_request(self, context: MiddlewareContext, call_next):
|
||||
if hasattr(context, 'fastmcp_context') and context.fastmcp_context:
|
||||
try:
|
||||
request = context.fastmcp_context.get_http_request()
|
||||
auth_header = request.headers.get("Authorization")
|
||||
|
||||
if not auth_header or not auth_header.startswith("Bearer "):
|
||||
raise ToolError("Missing or invalid authorization header")
|
||||
|
||||
token = auth_header.split(" ", 1)[1]
|
||||
if token != self.required_token:
|
||||
raise ToolError("Invalid authentication token")
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return await call_next(context)
|
||||
|
||||
# Usage
|
||||
mcp = FastMCP("SecureServer")
|
||||
mcp.add_middleware(AuthenticationMiddleware("secret-token-123"))
|
||||
```
|
||||
|
||||
### Performance Monitoring Middleware
|
||||
|
||||
This middleware tracks how long tools take to execute:
|
||||
Here's an example of how it works:
|
||||
|
||||
```python
|
||||
import time
|
||||
import logging
|
||||
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
||||
|
||||
class PerformanceMiddleware(Middleware):
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger("performance")
|
||||
|
||||
async def on_call_tool(self, context: MiddlewareContext, call_next):
|
||||
tool_name = context.message.name
|
||||
start_time = time.time()
|
||||
class SimpleTimingMiddleware(Middleware):
|
||||
async def on_request(self, context: MiddlewareContext, call_next):
|
||||
start_time = time.perf_counter()
|
||||
|
||||
try:
|
||||
result = await call_next(context)
|
||||
execution_time = time.time() - start_time
|
||||
|
||||
self.logger.info(
|
||||
f"Tool {tool_name} completed in {execution_time:.3f}s"
|
||||
)
|
||||
|
||||
duration_ms = (time.perf_counter() - start_time) * 1000
|
||||
print(f"Request {context.method} completed in {duration_ms:.2f}ms")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
execution_time = time.time() - start_time
|
||||
self.logger.error(
|
||||
f"Tool {tool_name} failed after {execution_time:.3f}s: {e}"
|
||||
)
|
||||
duration_ms = (time.perf_counter() - start_time) * 1000
|
||||
print(f"Request {context.method} failed after {duration_ms:.2f}ms: {e}")
|
||||
raise
|
||||
```
|
||||
|
||||
### Request Transformation Middleware
|
||||
|
||||
This middleware adds metadata to tool calls:
|
||||
To use the full version with proper logging and configuration:
|
||||
|
||||
```python
|
||||
class TransformationMiddleware(Middleware):
|
||||
async def on_call_tool(self, context: MiddlewareContext, call_next):
|
||||
if hasattr(context.message, 'arguments'):
|
||||
args = context.message.arguments or {}
|
||||
args['_middleware_timestamp'] = context.timestamp.isoformat()
|
||||
|
||||
modified_context = context.copy(
|
||||
message=context.message.model_copy(update={'arguments': args})
|
||||
)
|
||||
else:
|
||||
modified_context = context
|
||||
from fastmcp.server.middleware.timing import (
|
||||
TimingMiddleware,
|
||||
DetailedTimingMiddleware
|
||||
)
|
||||
|
||||
# Basic timing for all requests
|
||||
mcp.add_middleware(TimingMiddleware())
|
||||
|
||||
# Detailed per-operation timing (tools, resources, prompts)
|
||||
mcp.add_middleware(DetailedTimingMiddleware())
|
||||
```
|
||||
|
||||
The built-in versions include custom logger support, proper formatting, and **DetailedTimingMiddleware** provides operation-specific hooks like `on_call_tool` and `on_read_resource` for granular timing.
|
||||
|
||||
### Logging Middleware
|
||||
|
||||
Request and response logging is crucial for debugging, monitoring, and understanding usage patterns in your MCP server. FastMCP provides comprehensive logging middleware at `fastmcp.server.middleware.logging`.
|
||||
|
||||
Here's an example of how it works:
|
||||
|
||||
```python
|
||||
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
||||
|
||||
class SimpleLoggingMiddleware(Middleware):
|
||||
async def on_message(self, context: MiddlewareContext, call_next):
|
||||
print(f"Processing {context.method} from {context.source}")
|
||||
|
||||
return await call_next(modified_context)
|
||||
try:
|
||||
result = await call_next(context)
|
||||
print(f"Completed {context.method}")
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"Failed {context.method}: {e}")
|
||||
raise
|
||||
```
|
||||
|
||||
To use the full versions with advanced features:
|
||||
|
||||
```python
|
||||
from fastmcp.server.middleware.logging import (
|
||||
LoggingMiddleware,
|
||||
StructuredLoggingMiddleware
|
||||
)
|
||||
|
||||
# Human-readable logging with payload support
|
||||
mcp.add_middleware(LoggingMiddleware(
|
||||
include_payloads=True,
|
||||
max_payload_length=1000
|
||||
))
|
||||
|
||||
# JSON-structured logging for log aggregation tools
|
||||
mcp.add_middleware(StructuredLoggingMiddleware(include_payloads=True))
|
||||
```
|
||||
|
||||
The built-in versions include payload logging, structured JSON output, custom logger support, payload size limits, and operation-specific hooks for granular control.
|
||||
|
||||
### Rate Limiting Middleware
|
||||
|
||||
Rate limiting is essential for protecting your server from abuse, ensuring fair resource usage, and maintaining performance under load. FastMCP includes sophisticated rate limiting middleware at `fastmcp.server.middleware.rate_limiting`.
|
||||
|
||||
Here's an example of how it works:
|
||||
|
||||
```python
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
||||
from mcp import McpError
|
||||
from mcp.types import ErrorData
|
||||
|
||||
class SimpleRateLimitMiddleware(Middleware):
|
||||
def __init__(self, requests_per_minute: int = 60):
|
||||
self.requests_per_minute = requests_per_minute
|
||||
self.client_requests = defaultdict(list)
|
||||
|
||||
async def on_request(self, context: MiddlewareContext, call_next):
|
||||
current_time = time.time()
|
||||
client_id = "default" # In practice, extract from headers or context
|
||||
|
||||
# Clean old requests and check limit
|
||||
cutoff_time = current_time - 60
|
||||
self.client_requests[client_id] = [
|
||||
req_time for req_time in self.client_requests[client_id]
|
||||
if req_time > cutoff_time
|
||||
]
|
||||
|
||||
if len(self.client_requests[client_id]) >= self.requests_per_minute:
|
||||
raise McpError(ErrorData(code=-32000, message="Rate limit exceeded"))
|
||||
|
||||
self.client_requests[client_id].append(current_time)
|
||||
return await call_next(context)
|
||||
```
|
||||
|
||||
To use the full versions with advanced algorithms:
|
||||
|
||||
```python
|
||||
from fastmcp.server.middleware.rate_limiting import (
|
||||
RateLimitingMiddleware,
|
||||
SlidingWindowRateLimitingMiddleware
|
||||
)
|
||||
|
||||
# Token bucket rate limiting (allows controlled bursts)
|
||||
mcp.add_middleware(RateLimitingMiddleware(
|
||||
max_requests_per_second=10.0,
|
||||
burst_capacity=20
|
||||
))
|
||||
|
||||
# Sliding window rate limiting (precise time-based control)
|
||||
mcp.add_middleware(SlidingWindowRateLimitingMiddleware(
|
||||
max_requests=100,
|
||||
window_minutes=1
|
||||
))
|
||||
```
|
||||
|
||||
The built-in versions include token bucket algorithms, per-client identification, global rate limiting, and async-safe implementations with configurable client identification functions.
|
||||
|
||||
### Error Handling Middleware
|
||||
|
||||
Consistent error handling and recovery is critical for robust MCP servers. FastMCP provides comprehensive error handling middleware at `fastmcp.server.middleware.error_handling`.
|
||||
|
||||
Here's an example of how it works:
|
||||
|
||||
```python
|
||||
import logging
|
||||
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
||||
|
||||
class SimpleErrorHandlingMiddleware(Middleware):
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger("errors")
|
||||
self.error_counts = {}
|
||||
|
||||
async def on_message(self, context: MiddlewareContext, call_next):
|
||||
try:
|
||||
return await call_next(context)
|
||||
except Exception as error:
|
||||
# Log the error and track statistics
|
||||
error_key = f"{type(error).__name__}:{context.method}"
|
||||
self.error_counts[error_key] = self.error_counts.get(error_key, 0) + 1
|
||||
|
||||
self.logger.error(f"Error in {context.method}: {type(error).__name__}: {error}")
|
||||
raise
|
||||
```
|
||||
|
||||
To use the full versions with advanced features:
|
||||
|
||||
```python
|
||||
from fastmcp.server.middleware.error_handling import (
|
||||
ErrorHandlingMiddleware,
|
||||
RetryMiddleware
|
||||
)
|
||||
|
||||
# Comprehensive error logging and transformation
|
||||
mcp.add_middleware(ErrorHandlingMiddleware(
|
||||
include_traceback=True,
|
||||
transform_errors=True,
|
||||
error_callback=my_error_callback
|
||||
))
|
||||
|
||||
# Automatic retry with exponential backoff
|
||||
mcp.add_middleware(RetryMiddleware(
|
||||
max_retries=3,
|
||||
retry_exceptions=(ConnectionError, TimeoutError)
|
||||
))
|
||||
```
|
||||
|
||||
The built-in versions include error transformation, custom callbacks, configurable retry logic, and proper MCP error formatting.
|
||||
|
||||
### Combining Middleware
|
||||
|
||||
These middleware work together seamlessly:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.middleware.timing import TimingMiddleware
|
||||
from fastmcp.server.middleware.logging import LoggingMiddleware
|
||||
from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware
|
||||
from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware
|
||||
|
||||
mcp = FastMCP("Production Server")
|
||||
|
||||
# Add middleware in logical order
|
||||
mcp.add_middleware(ErrorHandlingMiddleware()) # Handle errors first
|
||||
mcp.add_middleware(RateLimitingMiddleware(max_requests_per_second=50))
|
||||
mcp.add_middleware(TimingMiddleware()) # Time actual execution
|
||||
mcp.add_middleware(LoggingMiddleware()) # Log everything
|
||||
|
||||
@mcp.tool
|
||||
def my_tool(data: str) -> str:
|
||||
return f"Processed: {data}"
|
||||
```
|
||||
|
||||
This configuration provides comprehensive monitoring, protection, and observability for your MCP server.
|
||||
|
||||
### Custom Middleware Example
|
||||
|
||||
You can also create custom middleware by extending the base class:
|
||||
|
||||
```python
|
||||
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
||||
|
||||
class CustomHeaderMiddleware(Middleware):
|
||||
async def on_request(self, context: MiddlewareContext, call_next):
|
||||
# Add custom logic here
|
||||
print(f"Processing {context.method}")
|
||||
|
||||
result = await call_next(context)
|
||||
|
||||
print(f"Completed {context.method}")
|
||||
return result
|
||||
|
||||
mcp.add_middleware(CustomHeaderMiddleware())
|
||||
```
|
||||
|
|
@ -57,6 +57,42 @@ def generate_code_request(language: str, task_description: str) -> PromptMessage
|
|||
Functions with `*args` or `**kwargs` are not supported as prompts. This restriction exists because FastMCP needs to generate a complete parameter schema for the MCP protocol, which isn't possible with variable argument lists.
|
||||
</Tip>
|
||||
|
||||
#### Decorator Arguments
|
||||
|
||||
While FastMCP infers the name and description from your function, you can override these and add additional metadata using arguments to the `@mcp.prompt` decorator:
|
||||
|
||||
```python
|
||||
@mcp.prompt(
|
||||
name="analyze_data_request", # Custom prompt name
|
||||
description="Creates a request to analyze data with specific parameters", # Custom description
|
||||
tags={"analysis", "data"} # Optional categorization tags
|
||||
)
|
||||
def data_analysis_prompt(
|
||||
data_uri: str = Field(description="The URI of the resource containing the data."),
|
||||
analysis_type: str = Field(default="summary", description="Type of analysis.")
|
||||
) -> str:
|
||||
"""This docstring is ignored when description is provided."""
|
||||
return f"Please perform a '{analysis_type}' analysis on the data found at {data_uri}."
|
||||
```
|
||||
|
||||
<Card icon="code" title="@prompt Decorator Arguments">
|
||||
<ParamField body="name" type="str | None">
|
||||
Sets the explicit prompt name exposed via MCP. If not provided, uses the function name
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="description" type="str | None">
|
||||
Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="tags" type="set[str] | None">
|
||||
A set of strings used to categorize the prompt. Clients might use tags to filter or group available prompts
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="enabled" type="bool" default="True">
|
||||
A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Argument Types
|
||||
|
||||
<VersionBadge version="2.9.0" />
|
||||
|
|
@ -177,28 +213,6 @@ def data_analysis_prompt(
|
|||
|
||||
In this example, the client *must* provide `data_uri`. If `analysis_type` or `include_charts` are omitted, their default values will be used.
|
||||
|
||||
### Prompt Metadata
|
||||
|
||||
While FastMCP infers the name and description from your function, you can override these and add tags using arguments to the `@mcp.prompt` decorator:
|
||||
|
||||
```python
|
||||
@mcp.prompt(
|
||||
name="analyze_data_request", # Custom prompt name
|
||||
description="Creates a request to analyze data with specific parameters", # Custom description
|
||||
tags={"analysis", "data"} # Optional categorization tags
|
||||
)
|
||||
def data_analysis_prompt(
|
||||
data_uri: str = Field(description="The URI of the resource containing the data."),
|
||||
analysis_type: str = Field(default="summary", description="Type of analysis.")
|
||||
) -> str:
|
||||
"""This docstring is ignored when description is provided."""
|
||||
return f"Please perform a '{analysis_type}' analysis on the data found at {data_uri}."
|
||||
```
|
||||
|
||||
- **`name`**: Sets the explicit prompt name exposed via MCP.
|
||||
- **`description`**: Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose.
|
||||
- **`tags`**: A set of strings used to categorize the prompt. Clients *might* use tags to filter or group available prompts.
|
||||
- **`enabled`**: A boolean to enable or disable the prompt (defaults to `True`). See [Disabling Prompts](#disabling-prompts) for more information.
|
||||
### Disabling Prompts
|
||||
|
||||
<VersionBadge version="2.8.0" />
|
||||
|
|
@ -224,7 +238,8 @@ def seasonal_prompt(): return "Happy Holidays!"
|
|||
seasonal_prompt.disable()
|
||||
seasonal_prompt.enable()
|
||||
```
|
||||
### Asynchronous Prompts
|
||||
|
||||
### Async Prompts
|
||||
|
||||
FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as prompts.
|
||||
|
||||
|
|
@ -267,7 +282,26 @@ async def generate_report_request(report_type: str, ctx: Context) -> str:
|
|||
|
||||
For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).
|
||||
|
||||
### Notifications
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
FastMCP automatically sends `notifications/prompts/list_changed` notifications to connected clients when prompts are added, enabled, or disabled. This allows clients to stay up-to-date with the current prompt set without manually polling for changes.
|
||||
|
||||
```python
|
||||
@mcp.prompt
|
||||
def example_prompt() -> str:
|
||||
return "Hello!"
|
||||
|
||||
# These operations trigger notifications:
|
||||
mcp.add_prompt(example_prompt) # Sends prompts/list_changed notification
|
||||
example_prompt.disable() # Sends prompts/list_changed notification
|
||||
example_prompt.enable() # Sends prompts/list_changed notification
|
||||
```
|
||||
|
||||
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.
|
||||
|
||||
Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their prompt lists or update their interfaces.
|
||||
|
||||
## Server Behavior
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ config = {
|
|||
"mcpServers": {
|
||||
"default": { # For single server configs, 'default' is commonly used
|
||||
"url": "https://example.com/mcp",
|
||||
"transport": "streamable-http"
|
||||
"transport": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,11 +145,11 @@ config = {
|
|||
"mcpServers": {
|
||||
"weather": {
|
||||
"url": "https://weather-api.example.com/mcp",
|
||||
"transport": "streamable-http"
|
||||
"transport": "http"
|
||||
},
|
||||
"calendar": {
|
||||
"url": "https://calendar-api.example.com/mcp",
|
||||
"transport": "streamable-http"
|
||||
"transport": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,18 +58,9 @@ def get_config() -> dict:
|
|||
* Resource Name: Taken from the function name (`get_greeting`).
|
||||
* Resource Description: Taken from the function's docstring.
|
||||
|
||||
### Return Values
|
||||
#### Decorator Arguments
|
||||
|
||||
FastMCP automatically converts your function's return value into the appropriate MCP resource content:
|
||||
|
||||
- **`str`**: Sent as `TextResourceContents` (with `mime_type="text/plain"` by default).
|
||||
- **`dict`, `list`, `pydantic.BaseModel`**: Automatically serialized to a JSON string and sent as `TextResourceContents` (with `mime_type="application/json"` by default).
|
||||
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents`. You should specify an appropriate `mime_type` (e.g., `"image/png"`, `"application/octet-stream"`).
|
||||
- **`None`**: Results in an empty resource content list being returned.
|
||||
|
||||
### Resource Metadata
|
||||
|
||||
You can customize the resource's properties using arguments in the decorator:
|
||||
You can customize the resource's properties using arguments in the `@mcp.resource` decorator:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -89,12 +80,40 @@ def get_application_status() -> dict:
|
|||
return {"status": "ok", "uptime": 12345, "version": mcp.settings.version} # Example usage
|
||||
```
|
||||
|
||||
- **`uri`**: The unique identifier for the resource (required).
|
||||
- **`name`**: A human-readable name (defaults to function name).
|
||||
- **`description`**: Explanation of the resource (defaults to docstring).
|
||||
- **`mime_type`**: Specifies the content type (FastMCP often infers a default like `text/plain` or `application/json`, but explicit is better for non-text types).
|
||||
- **`tags`**: A set of strings for categorization, potentially used by clients for filtering.
|
||||
- **`enabled`**: A boolean to enable or disable the resource (defaults to `True`). See [Disabling Resources](#disabling-resources) for more information.
|
||||
<Card icon="code" title="@resource Decorator Arguments">
|
||||
<ParamField body="uri" type="str" required>
|
||||
The unique identifier for the resource
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="name" type="str | None">
|
||||
A human-readable name. If not provided, defaults to function name
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="description" type="str | None">
|
||||
Explanation of the resource. If not provided, defaults to docstring
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="mime_type" type="str | None">
|
||||
Specifies the content type. FastMCP often infers a default like `text/plain` or `application/json`, but explicit is better for non-text types
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="tags" type="set[str] | None">
|
||||
A set of strings for categorization, potentially used by clients for filtering
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="enabled" type="bool" default="True">
|
||||
A boolean to enable or disable the resource. See [Disabling Resources](#disabling-resources) for more information
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Return Values
|
||||
|
||||
FastMCP automatically converts your function's return value into the appropriate MCP resource content:
|
||||
|
||||
- **`str`**: Sent as `TextResourceContents` (with `mime_type="text/plain"` by default).
|
||||
- **`dict`, `list`, `pydantic.BaseModel`**: Automatically serialized to a JSON string and sent as `TextResourceContents` (with `mime_type="application/json"` by default).
|
||||
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents`. You should specify an appropriate `mime_type` (e.g., `"image/png"`, `"application/octet-stream"`).
|
||||
- **`None`**: Results in an empty resource content list being returned.
|
||||
|
||||
### Disabling Resources
|
||||
|
||||
|
|
@ -122,6 +141,7 @@ get_config.disable()
|
|||
get_config.enable()
|
||||
```
|
||||
|
||||
|
||||
### Accessing MCP Context
|
||||
|
||||
<VersionBadge version="2.2.5" />
|
||||
|
|
@ -153,7 +173,7 @@ async def get_details(name: str, ctx: Context) -> dict:
|
|||
For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).
|
||||
|
||||
|
||||
### Asynchronous Resources
|
||||
### Async Resources
|
||||
|
||||
Use `async def` for resource functions that perform I/O operations (e.g., reading from a database or network) to avoid blocking the server.
|
||||
|
||||
|
|
@ -259,6 +279,27 @@ mcp.add_resource(special_resource, key="internal://data-v2") # Will be stored a
|
|||
|
||||
Note that this parameter is only available when using `add_resource()` directly and not through the `@resource` decorator, as URIs are provided explicitly when using the decorator.
|
||||
|
||||
### Notifications
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
FastMCP automatically sends `notifications/resources/list_changed` notifications to connected clients when resources or templates are added, enabled, or disabled. This allows clients to stay up-to-date with the current resource set without manually polling for changes.
|
||||
|
||||
```python
|
||||
@mcp.resource("data://example")
|
||||
def example_resource() -> str:
|
||||
return "Hello!"
|
||||
|
||||
# These operations trigger notifications:
|
||||
mcp.add_resource(example_resource) # Sends resources/list_changed notification
|
||||
example_resource.disable() # Sends resources/list_changed notification
|
||||
example_resource.enable() # Sends resources/list_changed notification
|
||||
```
|
||||
|
||||
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.
|
||||
|
||||
Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their resource lists or update their interfaces.
|
||||
|
||||
## Resource Templates
|
||||
|
||||
Resource Templates allow clients to request resources whose content depends on parameters embedded in the URI. Define a template using the **same `@mcp.resource` decorator**, but include `{parameter_name}` placeholders in the URI string and add corresponding arguments to your function signature.
|
||||
|
|
|
|||
|
|
@ -31,13 +31,31 @@ mcp_with_instructions = FastMCP(
|
|||
|
||||
The `FastMCP` constructor accepts several arguments:
|
||||
|
||||
* `name`: (Optional) A human-readable name for your server. Defaults to "FastMCP".
|
||||
* `instructions`: (Optional) Description of how to interact with this server. These instructions help clients understand the server's purpose and available functionality.
|
||||
* `lifespan`: (Optional) An async context manager function for server startup and shutdown logic.
|
||||
* `tags`: (Optional) A set of strings to tag the server itself.
|
||||
* `tools`: (Optional) A list of tools (or functions to convert to tools) to add to the server. In some cases, providing tools programmatically may be more convenient than using the `@mcp.tool` decorator.
|
||||
* `**settings`: Keyword arguments corresponding to additional `ServerSettings` configuration
|
||||
<Card icon="code" title="FastMCP Constructor Parameters">
|
||||
<ParamField body="name" type="str" default="FastMCP">
|
||||
A human-readable name for your server
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="instructions" type="str | None">
|
||||
Description of how to interact with this server. These instructions help clients understand the server's purpose and available functionality
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="lifespan" type="AsyncContextManager | None">
|
||||
An async context manager function for server startup and shutdown logic
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="tags" type="set[str] | None">
|
||||
A set of strings to tag the server itself
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="tools" type="list[Tool | Callable] | None">
|
||||
A list of tools (or functions to convert to tools) to add to the server. In some cases, providing tools programmatically may be more convenient than using the `@mcp.tool` decorator
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="**settings" type="Any">
|
||||
Keyword arguments corresponding to additional `ServerSettings` configuration
|
||||
</ParamField>
|
||||
</Card>
|
||||
## Components
|
||||
|
||||
FastMCP servers expose several types of components to the client:
|
||||
|
|
@ -158,8 +176,8 @@ if __name__ == "__main__":
|
|||
# This runs the server, defaulting to STDIO transport
|
||||
mcp.run()
|
||||
|
||||
# To use a different transport, e.g., HTTP:
|
||||
# mcp.run(transport="streamable-http", host="127.0.0.1", port=9000)
|
||||
# To use a different transport, e.g., Streamable HTTP:
|
||||
# mcp.run(transport="http", host="127.0.0.1", port=9000)
|
||||
```
|
||||
|
||||
FastMCP supports several transport options:
|
||||
|
|
@ -235,6 +253,34 @@ mcp = FastMCP(
|
|||
)
|
||||
```
|
||||
|
||||
### Constructor Parameters
|
||||
|
||||
<Card icon="code" title="AdditionalFastMCP Constructor Parameters">
|
||||
<ParamField body="dependencies" type="list[str] | None">
|
||||
Optional server dependencies list with package specifications
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="include_tags" type="set[str] | None">
|
||||
Only expose components with at least one matching tag
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="exclude_tags" type="set[str] | None">
|
||||
Hide components with any matching tag
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="on_duplicate_tools" type='Literal["error", "warn", "replace"]' default="error">
|
||||
How to handle duplicate tool registrations
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="on_duplicate_resources" type='Literal["error", "warn", "replace"]' default="warn">
|
||||
How to handle duplicate resource registrations
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="on_duplicate_prompts" type='Literal["error", "warn", "replace"]' default="replace">
|
||||
How to handle duplicate prompt registrations
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Global Settings
|
||||
|
||||
Global settings affect all FastMCP servers and can be configured via environment variables (prefixed with `FASTMCP_`) or in a `.env` file:
|
||||
|
|
@ -260,7 +306,7 @@ Transport settings are provided when running the server and control network beha
|
|||
```python
|
||||
# Configure transport when running
|
||||
mcp.run(
|
||||
transport="streamable-http",
|
||||
transport="http",
|
||||
host="0.0.0.0", # Bind to all interfaces
|
||||
port=9000, # Custom port
|
||||
log_level="DEBUG", # Override global log level
|
||||
|
|
@ -268,7 +314,7 @@ mcp.run(
|
|||
|
||||
# Or for async usage
|
||||
await mcp.run_async(
|
||||
transport="streamable-http",
|
||||
transport="http",
|
||||
host="127.0.0.1",
|
||||
port=8080,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,9 +49,68 @@ The way you define your Python function dictates how the tool appears and behave
|
|||
Functions with `*args` or `**kwargs` are not supported as tools. This restriction exists because FastMCP needs to generate a complete parameter schema for the MCP protocol, which isn't possible with variable argument lists.
|
||||
</Tip>
|
||||
|
||||
### Parameters
|
||||
#### Decorator Arguments
|
||||
|
||||
#### Annotations
|
||||
While FastMCP infers the name and description from your function, you can override these and add additional metadata using arguments to the `@mcp.tool` decorator:
|
||||
|
||||
```python
|
||||
@mcp.tool(
|
||||
name="find_products", # Custom tool name for the LLM
|
||||
description="Search the product catalog with optional category filtering.", # Custom description
|
||||
tags={"catalog", "search"}, # Optional tags for organization/filtering
|
||||
)
|
||||
def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
|
||||
"""Internal function description (ignored if description is provided above)."""
|
||||
# Implementation...
|
||||
print(f"Searching for '{query}' in category '{category}'")
|
||||
return [{"id": 2, "name": "Another Product"}]
|
||||
```
|
||||
|
||||
<Card icon="code" title="@tool Decorator Arguments">
|
||||
<ParamField body="name" type="str | None">
|
||||
Sets the explicit tool name exposed via MCP. If not provided, uses the function name
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="description" type="str | None">
|
||||
Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="tags" type="set[str] | None">
|
||||
A set of strings to categorize the tool. Clients might use tags to filter or group available tools
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="enabled" type="bool" default="True">
|
||||
A boolean to enable or disable the tool. See [Disabling Tools](#disabling-tools) for more information
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="exclude_args" type="list[str] | None">
|
||||
A list of argument names to exclude from the tool schema shown to the LLM. See [Excluding Arguments](#excluding-arguments) for more information
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="annotations" type="ToolAnnotations | dict | None">
|
||||
An optional `ToolAnnotations` object or dictionary to add additional metadata about the tool.
|
||||
<Expandable title="ToolAnnotations attributes">
|
||||
<ParamField body="title" type="str | None">
|
||||
A human-readable title for the tool.
|
||||
</ParamField>
|
||||
<ParamField body="readOnlyHint" type="bool | None">
|
||||
If true, the tool does not modify its environment.
|
||||
</ParamField>
|
||||
<ParamField body="destructiveHint" type="bool | None">
|
||||
If true, the tool may perform destructive updates to its environment.
|
||||
</ParamField>
|
||||
<ParamField body="idempotentHint" type="bool | None">
|
||||
If true, calling the tool repeatedly with the same arguments will have no additional effect on the its environment.
|
||||
</ParamField>
|
||||
<ParamField body="openWorldHint" type="bool | None">
|
||||
If true, this tool may interact with an "open world" of external entities. If false, the tool's domain of interaction is closed.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
</Card>
|
||||
### Tool Parameters
|
||||
|
||||
#### Type Annotations
|
||||
|
||||
Type annotations for parameters are essential for proper tool functionality. They:
|
||||
1. Inform the LLM about the expected data types for each parameter
|
||||
|
|
@ -150,28 +209,6 @@ def search_products(
|
|||
|
||||
In this example, the LLM must provide a `query` parameter, while `max_results`, `sort_by`, and `category` will use their default values if not explicitly provided.
|
||||
|
||||
### Metadata
|
||||
|
||||
While FastMCP infers the name and description from your function, you can override these and add tags using arguments to the `@mcp.tool` decorator:
|
||||
|
||||
```python
|
||||
@mcp.tool(
|
||||
name="find_products", # Custom tool name for the LLM
|
||||
description="Search the product catalog with optional category filtering.", # Custom description
|
||||
tags={"catalog", "search"}, # Optional tags for organization/filtering
|
||||
)
|
||||
def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
|
||||
"""Internal function description (ignored if description is provided above)."""
|
||||
# Implementation...
|
||||
print(f"Searching for '{query}' in category '{category}'")
|
||||
return [{"id": 2, "name": "Another Product"}]
|
||||
```
|
||||
|
||||
- **`name`**: Sets the explicit tool name exposed via MCP.
|
||||
- **`description`**: Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose.
|
||||
- **`tags`**: A set of strings to categorize the tool. Clients *might* use tags to filter or group available tools.
|
||||
- **`enabled`**: A boolean to enable or disable the tool (defaults to `True`). See [Disabling Tools](#disabling-tools) for more information.
|
||||
- **`exclude_args`**: A list of argument names to exclude from the tool schema shown to the LLM. See [Excluding Arguments](#excluding-arguments) for more information.
|
||||
|
||||
### Excluding Arguments
|
||||
|
||||
|
|
@ -251,56 +288,230 @@ Use `async def` when your tool needs to perform operations that might wait for e
|
|||
|
||||
### Return Values
|
||||
|
||||
FastMCP automatically converts the value returned by your function into the appropriate MCP content format for the client:
|
||||
|
||||
- **`str`**: Sent as `TextContent`.
|
||||
- **`dict`, `list`, Pydantic `BaseModel`**: Serialized to a JSON string and sent as `TextContent`.
|
||||
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (often within an `EmbeddedResource`).
|
||||
- **`fastmcp.utilities.types.Image`**: A helper class for easily returning image data. Sent as `ImageContent`.
|
||||
- **`fastmcp.utilities.types.Audio`**: A helper class for easily returning audio data. Sent as `AudioContent`.
|
||||
- **`fastmcp.utilities.types.File`**: A helper class for easily returning binary data as base64-encoded content. Sent as `EmbeddedResource`.
|
||||
- **A list of any of the above**: Automatically converts each item appropriately.
|
||||
- **`None`**: Results in an empty response (no content is sent back to the client).
|
||||
FastMCP tools can return data in two complementary formats: **traditional content blocks** (like text and images) and **structured outputs** (machine-readable JSON). When you add return type annotations, FastMCP automatically generates **output schemas** to validate the structured data and enables clients to deserialize results back to Python objects.
|
||||
|
||||
FastMCP will attempt to serialize other types to a string if possible.
|
||||
Understanding how these three concepts work together:
|
||||
|
||||
<Tip>
|
||||
At this time, FastMCP responds only to your tool's return *value*, not its return *annotation*.
|
||||
</Tip>
|
||||
- **Return Values**: What your Python function returns (determines both content blocks and structured data)
|
||||
- **Structured Outputs**: JSON data sent alongside traditional content for machine processing
|
||||
- **Output Schemas**: JSON Schema declarations that describe and validate the structured output format
|
||||
|
||||
The following sections explain each concept in detail.
|
||||
|
||||
#### Content Blocks
|
||||
|
||||
FastMCP automatically converts tool return values into appropriate MCP content blocks:
|
||||
|
||||
- **`str`**: Sent as `TextContent`
|
||||
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (within an `EmbeddedResource`)
|
||||
- **`fastmcp.utilities.types.Image`**: Sent as `ImageContent`
|
||||
- **`fastmcp.utilities.types.Audio`**: Sent as `AudioContent`
|
||||
- **`fastmcp.utilities.types.File`**: Sent as base64-encoded `EmbeddedResource`
|
||||
- **A list of any of the above**: Converts each item appropriately
|
||||
- **`None`**: Results in an empty response
|
||||
|
||||
#### Structured Output
|
||||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
The 6/18/2025 MCP spec update [introduced](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content) structured content, which is a new way to return data from tools. Structured content is a JSON object that is sent alongside traditional content. FastMCP automatically creates structured outputs alongside traditional content when your tool returns data that has a JSON object representation. This provides machine-readable JSON data that clients can deserialize back to Python objects.
|
||||
|
||||
**Automatic Structured Content Rules:**
|
||||
- **Object-like results** (`dict`, Pydantic models, dataclasses) → Always become structured content (even without output schema)
|
||||
- **Non-object results** (`int`, `str`, `list`) → Only become structured content if there's an output schema to validate/serialize them
|
||||
- **All results** → Always become traditional content blocks for backward compatibility
|
||||
|
||||
<Note>
|
||||
This automatic behavior enables clients to receive machine-readable data alongside human-readable content without requiring explicit output schemas for object-like returns.
|
||||
</Note>
|
||||
|
||||
##### Object-like Results (Automatic Structured Content)
|
||||
|
||||
<CodeGroup>
|
||||
```python Dict Return (No Schema Needed)
|
||||
@mcp.tool
|
||||
def get_user_data(user_id: str) -> dict:
|
||||
"""Get user data without type annotation."""
|
||||
return {"name": "Alice", "age": 30, "active": True}
|
||||
```
|
||||
|
||||
```json Traditional Content
|
||||
"{\n \"name\": \"Alice\",\n \"age\": 30,\n \"active\": true\n}"
|
||||
```
|
||||
|
||||
```json Structured Content (Automatic)
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
"active": true
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
##### Non-object Results (Schema Required)
|
||||
|
||||
<CodeGroup>
|
||||
```python Integer Return (No Schema)
|
||||
@mcp.tool
|
||||
def calculate_sum(a: int, b: int):
|
||||
"""Calculate sum without return annotation."""
|
||||
return a + b # Returns 8
|
||||
```
|
||||
|
||||
```json Traditional Content Only
|
||||
"8"
|
||||
```
|
||||
|
||||
```python Integer Return (With Schema)
|
||||
@mcp.tool
|
||||
def calculate_sum(a: int, b: int) -> int:
|
||||
"""Calculate sum with return annotation."""
|
||||
return a + b # Returns 8
|
||||
```
|
||||
|
||||
```json Traditional Content
|
||||
"8"
|
||||
```
|
||||
|
||||
```json Structured Content (From Schema)
|
||||
{
|
||||
"result": 8
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
##### Complex Type Example
|
||||
|
||||
<CodeGroup>
|
||||
```python Tool Definition
|
||||
from dataclasses import dataclass
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@dataclass
|
||||
class Person:
|
||||
name: str
|
||||
age: int
|
||||
email: str
|
||||
|
||||
@mcp.tool
|
||||
def get_user_profile(user_id: str) -> Person:
|
||||
"""Get a user's profile information."""
|
||||
return Person(name="Alice", age=30, email="alice@example.com")
|
||||
```
|
||||
|
||||
```json Generated Output Schema
|
||||
{
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"age": {"title": "Age", "type": "integer"},
|
||||
"email": {"title": "Email", "type": "string"}
|
||||
},
|
||||
"required": ["name", "age", "email"],
|
||||
"title": "Person",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
```json Structured Output
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
"email": "alice@example.com"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Output Schemas
|
||||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
||||
The 6/18/2025 MCP spec update [introduced](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema) output schemas, which are a new way to describe the expected output format of a tool. When an output schema is provided, the tool *must* return structured output that matches the schema.
|
||||
|
||||
When you add return type annotations to your functions, FastMCP automatically generates JSON schemas that describe the expected output format. These schemas help MCP clients understand and validate the structured data they receive.
|
||||
|
||||
##### Primitive Type Wrapping
|
||||
|
||||
For primitive return types (like `int`, `str`, `bool`), FastMCP automatically wraps the result under a `"result"` key to create valid structured output:
|
||||
|
||||
<CodeGroup>
|
||||
```python Primitive Return Type
|
||||
@mcp.tool
|
||||
def calculate_sum(a: int, b: int) -> int:
|
||||
"""Add two numbers together."""
|
||||
return a + b
|
||||
```
|
||||
|
||||
```json Generated Schema (Wrapped)
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "integer"}
|
||||
},
|
||||
"x-fastmcp-wrap-result": true
|
||||
}
|
||||
```
|
||||
|
||||
```json Structured Output
|
||||
{
|
||||
"result": 8
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
##### Manual Schema Control
|
||||
|
||||
You can override the automatically generated schema by providing a custom `output_schema`:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.utilities.types import Image
|
||||
import io
|
||||
|
||||
try:
|
||||
from PIL import Image as PILImage
|
||||
except ImportError:
|
||||
raise ImportError("Please install the `pillow` library to run this example.")
|
||||
|
||||
mcp = FastMCP("Image Demo")
|
||||
|
||||
@mcp.tool
|
||||
def generate_image(width: int, height: int, color: str) -> Image:
|
||||
"""Generates a solid color image."""
|
||||
# Create image using Pillow
|
||||
img = PILImage.new("RGB", (width, height), color=color)
|
||||
|
||||
# Save to a bytes buffer
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="PNG")
|
||||
img_bytes = buffer.getvalue()
|
||||
|
||||
# Return using FastMCP's Image helper
|
||||
return Image(data=img_bytes, format="png")
|
||||
|
||||
@mcp.tool
|
||||
def do_nothing() -> None:
|
||||
"""This tool performs an action but returns no data."""
|
||||
print("Performing a side effect...")
|
||||
return None
|
||||
@mcp.tool(output_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {"type": "string"},
|
||||
"metadata": {"type": "object"}
|
||||
}
|
||||
})
|
||||
def custom_schema_tool() -> dict:
|
||||
"""Tool with custom output schema."""
|
||||
return {"data": "Hello", "metadata": {"version": "1.0"}}
|
||||
```
|
||||
|
||||
Schema generation works for most common types including basic types, collections, union types, Pydantic models, TypedDict structures, and dataclasses.
|
||||
|
||||
<Warning>
|
||||
**Important Constraints**:
|
||||
- Output schemas must be object types (`"type": "object"`)
|
||||
- If you provide an output schema, your tool **must** return structured output that matches it
|
||||
- However, you can provide structured output without an output schema (using `ToolResult`)
|
||||
</Warning>
|
||||
|
||||
#### Full Control with ToolResult
|
||||
|
||||
For complete control over both traditional content and structured output, return a `ToolResult` object:
|
||||
|
||||
```python
|
||||
from fastmcp.tools.tool import ToolResult
|
||||
|
||||
@mcp.tool
|
||||
def advanced_tool() -> ToolResult:
|
||||
"""Tool with full control over output."""
|
||||
return ToolResult(
|
||||
content=[TextContent(text="Human-readable summary")],
|
||||
structured_content={"data": "value", "count": 42}
|
||||
)
|
||||
```
|
||||
|
||||
When returning `ToolResult`:
|
||||
- You control exactly what content and structured data is sent
|
||||
- Output schemas are optional - structured content can be provided without a schema
|
||||
- Clients receive both traditional content blocks and structured data
|
||||
|
||||
<Note>
|
||||
If your return type annotation cannot be converted to a JSON schema (e.g., complex custom classes without Pydantic support), the output schema will be omitted but the tool will still function normally with traditional content.
|
||||
</Note>
|
||||
|
||||
### Error Handling
|
||||
|
||||
<VersionBadge version="2.4.1" />
|
||||
|
|
@ -378,6 +589,28 @@ FastMCP supports these standard annotations:
|
|||
|
||||
Remember that annotations help make better user experiences but should be treated as advisory hints. They help client applications present appropriate UI elements and safety controls, but won't enforce security boundaries on their own. Always focus on making your annotations accurately represent what your tool actually does.
|
||||
|
||||
### Notifications
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
FastMCP automatically sends `notifications/tools/list_changed` notifications to connected clients when tools are added, removed, enabled, or disabled. This allows clients to stay up-to-date with the current tool set without manually polling for changes.
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
def example_tool() -> str:
|
||||
return "Hello!"
|
||||
|
||||
# These operations trigger notifications:
|
||||
mcp.add_tool(example_tool) # Sends tools/list_changed notification
|
||||
example_tool.disable() # Sends tools/list_changed notification
|
||||
example_tool.enable() # Sends tools/list_changed notification
|
||||
mcp.remove_tool("example_tool") # Sends tools/list_changed notification
|
||||
```
|
||||
|
||||
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.
|
||||
|
||||
Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their tool lists or update their interfaces.
|
||||
|
||||
## MCP Context
|
||||
|
||||
Tools can access MCP features like logging, reading resources, or reporting progress through the `Context` object. To use it, add a parameter to your tool function with the type hint `Context`.
|
||||
|
|
@ -797,13 +1030,3 @@ def calculate_sum(a: int, b: int) -> int:
|
|||
|
||||
mcp.remove_tool("calculate_sum")
|
||||
```
|
||||
|
||||
### Legacy JSON Parsing
|
||||
|
||||
<VersionBadge version="2.2.10" />
|
||||
|
||||
FastMCP 1.0 and < 2.2.10 relied on a crutch that attempted to work around LLM limitations by automatically parsing stringified JSON in tool arguments (e.g., converting `"[1,2,3]"` to `[1,2,3]`). As of FastMCP 2.2.10, this behavior is disabled by default because it circumvents type validation and can lead to unexpected type coercion issues (e.g. parsing "true" as a bool and attempting to call a tool that expected a string, which would fail type validation).
|
||||
|
||||
Most modern LLMs correctly format JSON, but if working with models that unnecessarily stringify JSON (as was the case with Claude Desktop in late 2024), you can re-enable this behavior on your server by setting the environment variable `FASTMCP_TOOL_ATTEMPT_PARSE_JSON_ARGS=1`.
|
||||
|
||||
We strongly recommend leaving this disabled unless necessary.
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ mcp = FastMCP.from_openapi(
|
|||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http", port=8000)
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
And that's it! With just a few lines of code, you've created an MCP server that exposes the entire JSONPlaceholder API as a collection of tools.
|
||||
|
|
@ -195,7 +195,7 @@ mcp = FastMCP.from_openapi(
|
|||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http", port=8000)
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
With this configuration:
|
||||
- `GET /users/{id}` becomes a `ResourceTemplate`.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,22 @@ icon: "sparkles"
|
|||
tag: NEW
|
||||
---
|
||||
|
||||
<Update label="FastMCP 2.9" description="June 23, 2025" tags={["Releases", "Blog Posts"]}>
|
||||
<Card
|
||||
title="FastMCP 2.9: MCP-Native Middleware" href="https://www.jlowin.dev/blog/fastmcp-2-9-middleware"
|
||||
img="https://jlowin.dev/_image?href=%2F_astro%2Fhero.BkVTdeBk.jpg&w=1200&h=630&f=png"
|
||||
cta="Read more"
|
||||
>
|
||||
FastMCP 2.9 is a major release that, among other things, introduces two important features that push beyond the basic MCP protocol.
|
||||
|
||||
🤝 *MCP Middleware* brings a flexible middleware system for intercepting and controlling server operations - think authentication, logging, rate limiting, and custom business logic without touching core protocol code.
|
||||
|
||||
✨ *Server-side type conversion* for prompts solves a major developer pain point: while MCP requires string arguments, your functions can now work with native Python types like lists and dictionaries, with automatic conversion handling the complexity.
|
||||
|
||||
These features transform FastMCP from a simple protocol implementation into a powerful framework for building sophisticated MCP applications. Combined with the new `File` utility for binary data and improvements to authentication and serialization, this release makes FastMCP significantly more flexible and developer-friendly while maintaining full protocol compliance.
|
||||
</Card>
|
||||
</Update>
|
||||
|
||||
<Update label="FastMCP 2.8" description="June 11, 2025" tags={["Releases", "Blog Posts"]}>
|
||||
<Card
|
||||
title="FastMCP 2.8: Transform and Roll Out" href="https://www.jlowin.dev/blog/fastmcp-2-8-tool-transformation"
|
||||
|
|
@ -107,7 +123,7 @@ img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.M_hv6gEB.png&w=1000&h=5
|
|||
cta="Read more"
|
||||
>
|
||||
|
||||
FastMCP 2.3 introduces full support for Streamable HTTP, a modern alternative to SSE that simplifies MCP deployments over the web. It’s efficient, reliable, and now the default HTTP transport. Just run your server with transport="streamable-http" and connect clients via a standard URL—FastMCP handles the rest. No special setup required. This release makes deploying MCP servers easier and more portable than ever.
|
||||
FastMCP 2.3 introduces full support for Streamable HTTP, a modern alternative to SSE that simplifies MCP deployments over the web. It’s efficient, reliable, and now the default HTTP transport. Just run your server with transport="http" and connect clients via a standard URL—FastMCP handles the rest. No special setup required. This release makes deploying MCP servers easier and more portable than ever.
|
||||
|
||||
</Card>
|
||||
</Update>
|
||||
|
|
|
|||
156
examples/atproto_mcp/README.md
Normal file
156
examples/atproto_mcp/README.md
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# ATProto MCP Server
|
||||
|
||||
This example demonstrates a FastMCP server that provides tools and resources for interacting with the AT Protocol (Bluesky).
|
||||
|
||||
## Features
|
||||
|
||||
### Resources (Read-only)
|
||||
|
||||
- **atproto://profile/status**: Get connection status and profile information
|
||||
- **atproto://timeline**: Retrieve your timeline feed
|
||||
- **atproto://notifications**: Get recent notifications
|
||||
|
||||
### Tools (Actions)
|
||||
|
||||
- **post**: Create posts with rich features (text, images, quotes, replies, links, mentions)
|
||||
- **create_thread**: Post multi-part threads with automatic linking
|
||||
- **search**: Search for posts by query
|
||||
- **follow**: Follow users by handle
|
||||
- **like**: Like posts by URI
|
||||
- **repost**: Share posts by URI
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create a `.env` file in the root directory with your Bluesky credentials:
|
||||
|
||||
```bash
|
||||
ATPROTO_HANDLE=your.handle@bsky.social
|
||||
ATPROTO_PASSWORD=your-app-password
|
||||
ATPROTO_PDS_URL=https://bsky.social # optional, defaults to bsky.social
|
||||
```
|
||||
|
||||
2. Install and run the server:
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
uv pip install -e .
|
||||
|
||||
# Run the server
|
||||
uv run atproto-mcp
|
||||
```
|
||||
|
||||
## The Unified Post Tool
|
||||
|
||||
The `post` tool is a single, flexible interface for all posting needs:
|
||||
|
||||
```python
|
||||
async def post(
|
||||
text: str, # Required: Post content
|
||||
images: list[str] = None, # Optional: Image URLs (max 4)
|
||||
image_alts: list[str] = None, # Optional: Alt text for images
|
||||
links: list[RichTextLink] = None, # Optional: Embedded links
|
||||
mentions: list[RichTextMention] = None, # Optional: User mentions
|
||||
reply_to: str = None, # Optional: Reply to post URI
|
||||
reply_root: str = None, # Optional: Thread root URI
|
||||
quote: str = None, # Optional: Quote post URI
|
||||
)
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from atproto_mcp.server import atproto_mcp
|
||||
|
||||
async def demo():
|
||||
async with Client(atproto_mcp) as client:
|
||||
# Simple post
|
||||
await client.call_tool("post", {
|
||||
"text": "Hello from FastMCP!"
|
||||
})
|
||||
|
||||
# Post with image
|
||||
await client.call_tool("post", {
|
||||
"text": "Beautiful sunset! 🌅",
|
||||
"images": ["https://example.com/sunset.jpg"],
|
||||
"image_alts": ["Sunset over the ocean"]
|
||||
})
|
||||
|
||||
# Reply to a post
|
||||
await client.call_tool("post", {
|
||||
"text": "Great point!",
|
||||
"reply_to": "at://did:plc:xxx/app.bsky.feed.post/yyy"
|
||||
})
|
||||
|
||||
# Quote post
|
||||
await client.call_tool("post", {
|
||||
"text": "This is important:",
|
||||
"quote": "at://did:plc:xxx/app.bsky.feed.post/yyy"
|
||||
})
|
||||
|
||||
# Rich text with links and mentions
|
||||
await client.call_tool("post", {
|
||||
"text": "Check out FastMCP by @alternatebuild.dev",
|
||||
"links": [{"text": "FastMCP", "url": "https://github.com/jlowin/fastmcp"}],
|
||||
"mentions": [{"handle": "alternatebuild.dev", "display_text": "@alternatebuild.dev"}]
|
||||
})
|
||||
|
||||
# Advanced: Quote with image
|
||||
await client.call_tool("post", {
|
||||
"text": "Adding visual context:",
|
||||
"quote": "at://did:plc:xxx/app.bsky.feed.post/yyy",
|
||||
"images": ["https://example.com/chart.png"]
|
||||
})
|
||||
|
||||
# Advanced: Reply with rich text
|
||||
await client.call_tool("post", {
|
||||
"text": "I agree! See this article for more info",
|
||||
"reply_to": "at://did:plc:xxx/app.bsky.feed.post/yyy",
|
||||
"links": [{"text": "this article", "url": "https://example.com/article"}]
|
||||
})
|
||||
|
||||
# Create a thread
|
||||
await client.call_tool("create_thread", {
|
||||
"posts": [
|
||||
{"text": "Starting a thread about Python 🧵"},
|
||||
{"text": "Python is great for rapid prototyping"},
|
||||
{"text": "And the ecosystem is amazing!", "images": ["https://example.com/python.jpg"]}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## AI Assistant Use Cases
|
||||
|
||||
The unified API enables natural AI assistant interactions:
|
||||
|
||||
- **"Reply to that post with these findings"** → Uses `reply_to` with rich text
|
||||
- **"Share this article with commentary"** → Uses `quote` with the article link
|
||||
- **"Post this chart with explanation"** → Uses `images` with descriptive text
|
||||
- **"Start a thread about AI safety"** → Uses `create_thread` for automatic linking
|
||||
|
||||
## Architecture
|
||||
|
||||
The server is organized as:
|
||||
- `server.py` - Public API with resources and tools
|
||||
- `_atproto/` - Private implementation module
|
||||
- `_client.py` - ATProto client management
|
||||
- `_posts.py` - Unified posting logic
|
||||
- `_profile.py` - Profile operations
|
||||
- `_read.py` - Timeline, search, notifications
|
||||
- `_social.py` - Follow, like, repost
|
||||
- `types.py` - TypedDict definitions
|
||||
- `settings.py` - Configuration management
|
||||
|
||||
## Running the Demo
|
||||
|
||||
```bash
|
||||
# Run demo (read-only)
|
||||
uv run python demo.py
|
||||
|
||||
# Run demo with posting enabled
|
||||
uv run python demo.py --post
|
||||
```
|
||||
|
||||
## Security Note
|
||||
|
||||
Store your Bluesky credentials securely in environment variables. Never commit credentials to version control.
|
||||
257
examples/atproto_mcp/demo.py
Normal file
257
examples/atproto_mcp/demo.py
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
"""Demo script showing all ATProto MCP server capabilities."""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
from typing import cast
|
||||
|
||||
from atproto_mcp.server import atproto_mcp
|
||||
from atproto_mcp.types import (
|
||||
NotificationsResult,
|
||||
PostResult,
|
||||
ProfileInfo,
|
||||
SearchResult,
|
||||
TimelineResult,
|
||||
)
|
||||
|
||||
from fastmcp import Client
|
||||
|
||||
|
||||
async def main(enable_posting: bool = False):
|
||||
print("🔵 ATProto MCP Server Demo\n")
|
||||
|
||||
async with Client(atproto_mcp) as client:
|
||||
# 1. Check connection status (resource)
|
||||
print("1. Checking connection status...")
|
||||
result = await client.read_resource("atproto://profile/status")
|
||||
status: ProfileInfo = (
|
||||
json.loads(result[0].text) if result else cast(ProfileInfo, {})
|
||||
)
|
||||
|
||||
if status.get("connected"):
|
||||
print(f"✅ Connected as: @{status['handle']}")
|
||||
print(f" Followers: {status['followers']}")
|
||||
print(f" Following: {status['following']}")
|
||||
print(f" Posts: {status['posts']}")
|
||||
else:
|
||||
print(f"❌ Connection failed: {status.get('error')}")
|
||||
return
|
||||
|
||||
# 2. Get timeline
|
||||
print("\n2. Getting timeline...")
|
||||
result = await client.read_resource("atproto://timeline")
|
||||
timeline: TimelineResult = (
|
||||
json.loads(result[0].text) if result else cast(TimelineResult, {})
|
||||
)
|
||||
|
||||
if timeline.get("success") and timeline["posts"]:
|
||||
print(f"✅ Found {timeline['count']} posts")
|
||||
post = timeline["posts"][0]
|
||||
print(f" Latest by @{post['author']}: {post['text'][:80]}...")
|
||||
save_uri = post["uri"] # Save for later interactions
|
||||
else:
|
||||
print("❌ No posts in timeline")
|
||||
save_uri = None
|
||||
|
||||
# 3. Search for posts
|
||||
print("\n3. Searching for posts about 'Bluesky'...")
|
||||
result = await client.call_tool("search", {"query": "Bluesky", "limit": 5})
|
||||
search: SearchResult = (
|
||||
json.loads(result[0].text) if result else cast(SearchResult, {})
|
||||
)
|
||||
|
||||
if search.get("success") and search["posts"]:
|
||||
print(f"✅ Found {search['count']} posts")
|
||||
print(f" Sample: {search['posts'][0]['text'][:80]}...")
|
||||
|
||||
# 4. Get notifications
|
||||
print("\n4. Checking notifications...")
|
||||
result = await client.read_resource("atproto://notifications")
|
||||
notifs: NotificationsResult = (
|
||||
json.loads(result[0].text) if result else cast(NotificationsResult, {})
|
||||
)
|
||||
|
||||
if notifs.get("success"):
|
||||
print(f"✅ You have {notifs['count']} notifications")
|
||||
unread = sum(1 for n in notifs["notifications"] if not n["is_read"])
|
||||
if unread:
|
||||
print(f" ({unread} unread)")
|
||||
|
||||
# 5. Demo posting capabilities
|
||||
if enable_posting:
|
||||
print("\n5. Demonstrating posting capabilities...")
|
||||
|
||||
# a. Simple post
|
||||
print("\n a) Creating a simple post...")
|
||||
result = await client.call_tool(
|
||||
"post",
|
||||
{"text": "🧪 Testing the unified ATProto MCP post tool! #FastMCP"},
|
||||
)
|
||||
post_result: PostResult = json.loads(result[0].text) if result else {}
|
||||
if post_result.get("success"):
|
||||
print(" ✅ Posted successfully!")
|
||||
simple_uri = post_result["uri"]
|
||||
else:
|
||||
print(f" ❌ Failed: {post_result.get('error')}")
|
||||
simple_uri = None
|
||||
|
||||
# b. Post with rich text (link and mention)
|
||||
print("\n b) Creating a post with rich text...")
|
||||
result = await client.call_tool(
|
||||
"post",
|
||||
{
|
||||
"text": "Check out FastMCP and follow @alternatebuild.dev for updates!",
|
||||
"links": [
|
||||
{"text": "FastMCP", "url": "https://github.com/jlowin/fastmcp"}
|
||||
],
|
||||
"mentions": [
|
||||
{
|
||||
"handle": "alternatebuild.dev",
|
||||
"display_text": "@alternatebuild.dev",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
if json.loads(result[0].text).get("success"):
|
||||
print(" ✅ Rich text post created!")
|
||||
|
||||
# c. Reply to a post
|
||||
if save_uri:
|
||||
print("\n c) Replying to a post...")
|
||||
result = await client.call_tool(
|
||||
"post", {"text": "Great post! 👍", "reply_to": save_uri}
|
||||
)
|
||||
if json.loads(result[0].text).get("success"):
|
||||
print(" ✅ Reply posted!")
|
||||
|
||||
# d. Quote post
|
||||
if simple_uri:
|
||||
print("\n d) Creating a quote post...")
|
||||
result = await client.call_tool(
|
||||
"post",
|
||||
{
|
||||
"text": "Quoting my own test post for demo purposes 🔄",
|
||||
"quote": simple_uri,
|
||||
},
|
||||
)
|
||||
if json.loads(result[0].text).get("success"):
|
||||
print(" ✅ Quote post created!")
|
||||
|
||||
# e. Post with image
|
||||
print("\n e) Creating a post with image...")
|
||||
result = await client.call_tool(
|
||||
"post",
|
||||
{
|
||||
"text": "Here's a test image post! 📸",
|
||||
"images": ["https://picsum.photos/400/300"],
|
||||
"image_alts": ["Random test image"],
|
||||
},
|
||||
)
|
||||
if json.loads(result[0].text).get("success"):
|
||||
print(" ✅ Image post created!")
|
||||
|
||||
# f. Quote with image (advanced)
|
||||
if simple_uri:
|
||||
print("\n f) Creating a quote post with image...")
|
||||
result = await client.call_tool(
|
||||
"post",
|
||||
{
|
||||
"text": "Quote + image combo! 🎨",
|
||||
"quote": simple_uri,
|
||||
"images": ["https://picsum.photos/300/200"],
|
||||
"image_alts": ["Another test image"],
|
||||
},
|
||||
)
|
||||
if json.loads(result[0].text).get("success"):
|
||||
print(" ✅ Quote with image created!")
|
||||
|
||||
# g. Social actions
|
||||
if save_uri:
|
||||
print("\n g) Demonstrating social actions...")
|
||||
|
||||
# Like
|
||||
result = await client.call_tool("like", {"uri": save_uri})
|
||||
if json.loads(result[0].text).get("success"):
|
||||
print(" ✅ Liked a post!")
|
||||
|
||||
# Repost
|
||||
result = await client.call_tool("repost", {"uri": save_uri})
|
||||
if json.loads(result[0].text).get("success"):
|
||||
print(" ✅ Reposted!")
|
||||
|
||||
# Follow
|
||||
result = await client.call_tool(
|
||||
"follow", {"handle": "alternatebuild.dev"}
|
||||
)
|
||||
if json.loads(result[0].text).get("success"):
|
||||
print(" ✅ Followed @alternatebuild.dev!")
|
||||
|
||||
# h. Thread creation (new!)
|
||||
print("\n h) Creating a thread...")
|
||||
result = await client.call_tool(
|
||||
"create_thread",
|
||||
{
|
||||
"posts": [
|
||||
{
|
||||
"text": "Let me share some thoughts about the ATProto MCP server 🧵"
|
||||
},
|
||||
{
|
||||
"text": "First, it makes posting from the terminal incredibly smooth"
|
||||
},
|
||||
{
|
||||
"text": "The unified post API means one tool handles everything",
|
||||
"links": [
|
||||
{
|
||||
"text": "everything",
|
||||
"url": "https://github.com/jlowin/fastmcp",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"text": "And now with create_thread, multi-post threads are trivial!"
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
if json.loads(result[0].text).get("success"):
|
||||
thread_result = json.loads(result[0].text)
|
||||
print(f" ✅ Thread created with {thread_result['post_count']} posts!")
|
||||
else:
|
||||
print("\n5. Posting capabilities (not enabled):")
|
||||
print(" To test posting, run with --post flag")
|
||||
print(" Example: python demo.py --post")
|
||||
|
||||
# 6. Show available capabilities
|
||||
print("\n6. Available capabilities:")
|
||||
print("\n Resources (read-only):")
|
||||
print(" - atproto://profile/status")
|
||||
print(" - atproto://timeline")
|
||||
print(" - atproto://notifications")
|
||||
|
||||
print("\n Tools (actions):")
|
||||
print(" - post: Unified posting with rich features")
|
||||
print(" • Simple text posts")
|
||||
print(" • Images (up to 4)")
|
||||
print(" • Rich text (links, mentions)")
|
||||
print(" • Replies and threads")
|
||||
print(" • Quote posts")
|
||||
print(" • Combinations (quote + image, reply + rich text, etc.)")
|
||||
print(" - search: Search for posts")
|
||||
print(" - create_thread: Post multi-part threads")
|
||||
print(" - follow: Follow users")
|
||||
print(" - like: Like posts")
|
||||
print(" - repost: Share posts")
|
||||
|
||||
print("\n✨ Demo complete!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="ATProto MCP Server Demo")
|
||||
parser.add_argument(
|
||||
"--post",
|
||||
action="store_true",
|
||||
help="Enable posting test messages to Bluesky",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
asyncio.run(main(enable_posting=args.post))
|
||||
24
examples/atproto_mcp/pyproject.toml
Normal file
24
examples/atproto_mcp/pyproject.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[project]
|
||||
name = "atproto-mcp"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "zzstoatzz", email = "thrast36@gmail.com" }]
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"fastmcp>=0.8.0",
|
||||
"atproto@git+https://github.com/MarshalX/atproto.git@refs/pull/605/head",
|
||||
"pydantic-settings>=2.0.0",
|
||||
"websockets>=15.0.1",
|
||||
"httpx>=0.27.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
atproto-mcp = "atproto_mcp.__main__:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.metadata]
|
||||
allow-direct-references = true
|
||||
3
examples/atproto_mcp/src/atproto_mcp/__init__.py
Normal file
3
examples/atproto_mcp/src/atproto_mcp/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from atproto_mcp.settings import settings
|
||||
|
||||
__all__ = ["settings"]
|
||||
9
examples/atproto_mcp/src/atproto_mcp/__main__.py
Normal file
9
examples/atproto_mcp/src/atproto_mcp/__main__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from atproto_mcp.server import atproto_mcp
|
||||
|
||||
|
||||
def main():
|
||||
atproto_mcp.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
20
examples/atproto_mcp/src/atproto_mcp/_atproto/__init__.py
Normal file
20
examples/atproto_mcp/src/atproto_mcp/_atproto/__init__.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
"""Private ATProto implementation module."""
|
||||
|
||||
from ._client import get_client
|
||||
from ._posts import create_post, create_thread
|
||||
from ._profile import get_profile_info
|
||||
from ._read import fetch_notifications, fetch_timeline, search_for_posts
|
||||
from ._social import follow_user_by_handle, like_post_by_uri, repost_by_uri
|
||||
|
||||
__all__ = [
|
||||
"get_client",
|
||||
"get_profile_info",
|
||||
"create_post",
|
||||
"create_thread",
|
||||
"fetch_timeline",
|
||||
"search_for_posts",
|
||||
"fetch_notifications",
|
||||
"follow_user_by_handle",
|
||||
"like_post_by_uri",
|
||||
"repost_by_uri",
|
||||
]
|
||||
16
examples/atproto_mcp/src/atproto_mcp/_atproto/_client.py
Normal file
16
examples/atproto_mcp/src/atproto_mcp/_atproto/_client.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""ATProto client management."""
|
||||
|
||||
from atproto import Client
|
||||
|
||||
from atproto_mcp.settings import settings
|
||||
|
||||
_client: Client | None = None
|
||||
|
||||
|
||||
def get_client() -> Client:
|
||||
"""Get or create an authenticated ATProto client."""
|
||||
global _client
|
||||
if _client is None:
|
||||
_client = Client()
|
||||
_client.login(settings.atproto_handle, settings.atproto_password)
|
||||
return _client
|
||||
385
examples/atproto_mcp/src/atproto_mcp/_atproto/_posts.py
Normal file
385
examples/atproto_mcp/src/atproto_mcp/_atproto/_posts.py
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
"""Unified posting functionality."""
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from atproto import models
|
||||
|
||||
from atproto_mcp.types import (
|
||||
PostResult,
|
||||
RichTextLink,
|
||||
RichTextMention,
|
||||
ThreadPost,
|
||||
ThreadResult,
|
||||
)
|
||||
|
||||
from ._client import get_client
|
||||
|
||||
|
||||
def create_post(
|
||||
text: str,
|
||||
images: list[str] | None = None,
|
||||
image_alts: list[str] | None = None,
|
||||
links: list[RichTextLink] | None = None,
|
||||
mentions: list[RichTextMention] | None = None,
|
||||
reply_to: str | None = None,
|
||||
reply_root: str | None = None,
|
||||
quote: str | None = None,
|
||||
) -> PostResult:
|
||||
"""Create a unified post with optional features.
|
||||
|
||||
Args:
|
||||
text: Post text (max 300 chars)
|
||||
images: URLs of images to attach (max 4)
|
||||
image_alts: Alt text for images
|
||||
links: Links to embed in rich text
|
||||
mentions: User mentions to embed
|
||||
reply_to: URI of post to reply to
|
||||
reply_root: URI of thread root (defaults to reply_to)
|
||||
quote: URI of post to quote
|
||||
"""
|
||||
try:
|
||||
client = get_client()
|
||||
facets = []
|
||||
embed = None
|
||||
reply_ref = None
|
||||
|
||||
# Handle rich text facets (links and mentions)
|
||||
if links or mentions:
|
||||
facets = _build_facets(text, links, mentions, client)
|
||||
|
||||
# Handle replies
|
||||
if reply_to:
|
||||
reply_ref = _build_reply_ref(reply_to, reply_root, client)
|
||||
|
||||
# Handle quotes and images
|
||||
if quote and images:
|
||||
# Quote with images - create record with media embed
|
||||
embed = _build_quote_with_images_embed(quote, images, image_alts, client)
|
||||
elif quote:
|
||||
# Quote only
|
||||
embed = _build_quote_embed(quote, client)
|
||||
elif images:
|
||||
# Images only - use send_images for proper handling
|
||||
return _send_images(text, images, image_alts, facets, reply_ref, client)
|
||||
|
||||
# Send the post
|
||||
post = client.send_post(
|
||||
text=text,
|
||||
facets=facets if facets else None,
|
||||
embed=embed,
|
||||
reply_to=reply_ref,
|
||||
)
|
||||
|
||||
return PostResult(
|
||||
success=True,
|
||||
uri=post.uri,
|
||||
cid=post.cid,
|
||||
text=text,
|
||||
created_at=datetime.now().isoformat(),
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
return PostResult(
|
||||
success=False,
|
||||
uri=None,
|
||||
cid=None,
|
||||
text=None,
|
||||
created_at=None,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
|
||||
def _build_facets(
|
||||
text: str,
|
||||
links: list[RichTextLink] | None,
|
||||
mentions: list[RichTextMention] | None,
|
||||
client,
|
||||
):
|
||||
"""Build facets for rich text formatting."""
|
||||
facets = []
|
||||
|
||||
# Process links
|
||||
if links:
|
||||
for link in links:
|
||||
start = text.find(link["text"])
|
||||
if start == -1:
|
||||
continue
|
||||
end = start + len(link["text"])
|
||||
|
||||
facets.append(
|
||||
models.AppBskyRichtextFacet.Main(
|
||||
features=[models.AppBskyRichtextFacet.Link(uri=link["url"])],
|
||||
index=models.AppBskyRichtextFacet.ByteSlice(
|
||||
byte_start=len(text[:start].encode("UTF-8")),
|
||||
byte_end=len(text[:end].encode("UTF-8")),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Process mentions
|
||||
if mentions:
|
||||
for mention in mentions:
|
||||
display_text = mention.get("display_text") or f"@{mention['handle']}"
|
||||
start = text.find(display_text)
|
||||
if start == -1:
|
||||
continue
|
||||
end = start + len(display_text)
|
||||
|
||||
# Resolve handle to DID
|
||||
resolved = client.app.bsky.actor.search_actors(
|
||||
params={"q": mention["handle"], "limit": 1}
|
||||
)
|
||||
if not resolved.actors:
|
||||
continue
|
||||
|
||||
did = resolved.actors[0].did
|
||||
facets.append(
|
||||
models.AppBskyRichtextFacet.Main(
|
||||
features=[models.AppBskyRichtextFacet.Mention(did=did)],
|
||||
index=models.AppBskyRichtextFacet.ByteSlice(
|
||||
byte_start=len(text[:start].encode("UTF-8")),
|
||||
byte_end=len(text[:end].encode("UTF-8")),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return facets
|
||||
|
||||
|
||||
def _build_reply_ref(reply_to: str, reply_root: str | None, client):
|
||||
"""Build reply reference."""
|
||||
# Get parent post to extract CID
|
||||
parent_post = client.app.bsky.feed.get_posts(params={"uris": [reply_to]})
|
||||
if not parent_post.posts:
|
||||
raise ValueError("Parent post not found")
|
||||
|
||||
parent_cid = parent_post.posts[0].cid
|
||||
parent_ref = models.ComAtprotoRepoStrongRef.Main(uri=reply_to, cid=parent_cid)
|
||||
|
||||
# If no root_uri provided, parent is the root
|
||||
if reply_root is None:
|
||||
root_ref = parent_ref
|
||||
else:
|
||||
# Get root post CID
|
||||
root_post = client.app.bsky.feed.get_posts(params={"uris": [reply_root]})
|
||||
if not root_post.posts:
|
||||
raise ValueError("Root post not found")
|
||||
root_cid = root_post.posts[0].cid
|
||||
root_ref = models.ComAtprotoRepoStrongRef.Main(uri=reply_root, cid=root_cid)
|
||||
|
||||
return models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref)
|
||||
|
||||
|
||||
def _build_quote_embed(quote_uri: str, client):
|
||||
"""Build quote embed."""
|
||||
# Get the post to quote
|
||||
quoted_post = client.app.bsky.feed.get_posts(params={"uris": [quote_uri]})
|
||||
if not quoted_post.posts:
|
||||
raise ValueError("Quoted post not found")
|
||||
|
||||
# Create strong ref for the quoted post
|
||||
quoted_cid = quoted_post.posts[0].cid
|
||||
quoted_ref = models.ComAtprotoRepoStrongRef.Main(uri=quote_uri, cid=quoted_cid)
|
||||
|
||||
# Create the embed
|
||||
return models.AppBskyEmbedRecord.Main(record=quoted_ref)
|
||||
|
||||
|
||||
def _build_quote_with_images_embed(
|
||||
quote_uri: str, image_urls: list[str], image_alts: list[str] | None, client
|
||||
):
|
||||
"""Build quote embed with images."""
|
||||
import httpx
|
||||
|
||||
# Get the quoted post
|
||||
quoted_post = client.app.bsky.feed.get_posts(params={"uris": [quote_uri]})
|
||||
if not quoted_post.posts:
|
||||
raise ValueError("Quoted post not found")
|
||||
|
||||
quoted_cid = quoted_post.posts[0].cid
|
||||
quoted_ref = models.ComAtprotoRepoStrongRef.Main(uri=quote_uri, cid=quoted_cid)
|
||||
|
||||
# Download and upload images
|
||||
images = []
|
||||
alts = image_alts or [""] * len(image_urls)
|
||||
|
||||
for i, url in enumerate(image_urls[:4]):
|
||||
response = httpx.get(url, follow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
# Upload to blob storage
|
||||
upload = client.upload_blob(response.content)
|
||||
images.append(
|
||||
models.AppBskyEmbedImages.Image(
|
||||
alt=alts[i] if i < len(alts) else "",
|
||||
image=upload.blob,
|
||||
)
|
||||
)
|
||||
|
||||
# Create record with media embed
|
||||
return models.AppBskyEmbedRecordWithMedia.Main(
|
||||
record=models.AppBskyEmbedRecord.Main(record=quoted_ref),
|
||||
media=models.AppBskyEmbedImages.Main(images=images),
|
||||
)
|
||||
|
||||
|
||||
def _send_images(
|
||||
text: str,
|
||||
image_urls: list[str],
|
||||
image_alts: list[str] | None,
|
||||
facets,
|
||||
reply_ref,
|
||||
client,
|
||||
):
|
||||
"""Send post with images using the client's send_images method."""
|
||||
import httpx
|
||||
|
||||
# Ensure alt_texts has same length as images
|
||||
if image_alts is None:
|
||||
image_alts = [""] * len(image_urls)
|
||||
elif len(image_alts) < len(image_urls):
|
||||
image_alts.extend([""] * (len(image_urls) - len(image_alts)))
|
||||
|
||||
image_data = []
|
||||
alts = []
|
||||
for i, url in enumerate(image_urls[:4]): # Max 4 images
|
||||
# Download image (follow redirects)
|
||||
response = httpx.get(url, follow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
image_data.append(response.content)
|
||||
alts.append(image_alts[i] if i < len(image_alts) else "")
|
||||
|
||||
# Send post with images
|
||||
# Note: send_images doesn't support facets or reply_to directly
|
||||
# So we need to use send_post with manual image upload if we have those
|
||||
if facets or reply_ref:
|
||||
# Manual image upload
|
||||
images = []
|
||||
for i, data in enumerate(image_data):
|
||||
upload = client.upload_blob(data)
|
||||
images.append(
|
||||
models.AppBskyEmbedImages.Image(
|
||||
alt=alts[i],
|
||||
image=upload.blob,
|
||||
)
|
||||
)
|
||||
|
||||
embed = models.AppBskyEmbedImages.Main(images=images)
|
||||
post = client.send_post(
|
||||
text=text,
|
||||
facets=facets if facets else None,
|
||||
embed=embed,
|
||||
reply_to=reply_ref,
|
||||
)
|
||||
else:
|
||||
# Use simple send_images
|
||||
post = client.send_images(
|
||||
text=text,
|
||||
images=image_data,
|
||||
image_alts=alts,
|
||||
)
|
||||
|
||||
return PostResult(
|
||||
success=True,
|
||||
uri=post.uri,
|
||||
cid=post.cid,
|
||||
text=text,
|
||||
created_at=datetime.now().isoformat(),
|
||||
error=None,
|
||||
)
|
||||
|
||||
|
||||
def create_thread(posts: list[ThreadPost]) -> ThreadResult:
|
||||
"""Create a thread of posts with automatic linking.
|
||||
|
||||
Args:
|
||||
posts: List of posts to create as a thread. First post is the root.
|
||||
"""
|
||||
if not posts:
|
||||
return ThreadResult(
|
||||
success=False,
|
||||
thread_uri=None,
|
||||
post_uris=[],
|
||||
post_count=0,
|
||||
error="No posts provided",
|
||||
)
|
||||
|
||||
try:
|
||||
post_uris = []
|
||||
root_uri = None
|
||||
parent_uri = None
|
||||
|
||||
for i, post_data in enumerate(posts):
|
||||
# First post is the root
|
||||
if i == 0:
|
||||
result = create_post(
|
||||
text=post_data["text"],
|
||||
images=post_data.get("images"),
|
||||
image_alts=post_data.get("image_alts"),
|
||||
links=post_data.get("links"),
|
||||
mentions=post_data.get("mentions"),
|
||||
quote=post_data.get("quote"),
|
||||
)
|
||||
|
||||
if not result["success"]:
|
||||
return ThreadResult(
|
||||
success=False,
|
||||
thread_uri=None,
|
||||
post_uris=post_uris,
|
||||
post_count=len(post_uris),
|
||||
error=f"Failed to create root post: {result['error']}",
|
||||
)
|
||||
|
||||
root_uri = result["uri"]
|
||||
parent_uri = root_uri
|
||||
post_uris.append(root_uri)
|
||||
|
||||
# Small delay to ensure post is indexed
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
# Subsequent posts reply to the previous one
|
||||
result = create_post(
|
||||
text=post_data["text"],
|
||||
images=post_data.get("images"),
|
||||
image_alts=post_data.get("image_alts"),
|
||||
links=post_data.get("links"),
|
||||
mentions=post_data.get("mentions"),
|
||||
quote=post_data.get("quote"),
|
||||
reply_to=parent_uri,
|
||||
reply_root=root_uri,
|
||||
)
|
||||
|
||||
if not result["success"]:
|
||||
return ThreadResult(
|
||||
success=False,
|
||||
thread_uri=root_uri,
|
||||
post_uris=post_uris,
|
||||
post_count=len(post_uris),
|
||||
error=f"Failed to create post {i + 1}: {result['error']}",
|
||||
)
|
||||
|
||||
parent_uri = result["uri"]
|
||||
post_uris.append(parent_uri)
|
||||
|
||||
# Small delay between posts
|
||||
if i < len(posts) - 1:
|
||||
time.sleep(0.5)
|
||||
|
||||
return ThreadResult(
|
||||
success=True,
|
||||
thread_uri=root_uri,
|
||||
post_uris=post_uris,
|
||||
post_count=len(post_uris),
|
||||
error=None,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return ThreadResult(
|
||||
success=False,
|
||||
thread_uri=None,
|
||||
post_uris=post_uris,
|
||||
post_count=len(post_uris),
|
||||
error=str(e),
|
||||
)
|
||||
33
examples/atproto_mcp/src/atproto_mcp/_atproto/_profile.py
Normal file
33
examples/atproto_mcp/src/atproto_mcp/_atproto/_profile.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""Profile-related operations."""
|
||||
|
||||
from atproto_mcp.types import ProfileInfo
|
||||
|
||||
from ._client import get_client
|
||||
|
||||
|
||||
def get_profile_info() -> ProfileInfo:
|
||||
"""Get profile information for the authenticated user."""
|
||||
try:
|
||||
client = get_client()
|
||||
profile = client.get_profile(client.me.did)
|
||||
return ProfileInfo(
|
||||
connected=True,
|
||||
handle=profile.handle,
|
||||
display_name=profile.display_name,
|
||||
did=client.me.did,
|
||||
followers=profile.followers_count,
|
||||
following=profile.follows_count,
|
||||
posts=profile.posts_count,
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
return ProfileInfo(
|
||||
connected=False,
|
||||
handle=None,
|
||||
display_name=None,
|
||||
did=None,
|
||||
followers=None,
|
||||
following=None,
|
||||
posts=None,
|
||||
error=str(e),
|
||||
)
|
||||
124
examples/atproto_mcp/src/atproto_mcp/_atproto/_read.py
Normal file
124
examples/atproto_mcp/src/atproto_mcp/_atproto/_read.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""Read-only operations for timeline, search, and notifications."""
|
||||
|
||||
from atproto_mcp.types import (
|
||||
Notification,
|
||||
NotificationsResult,
|
||||
Post,
|
||||
SearchResult,
|
||||
TimelineResult,
|
||||
)
|
||||
|
||||
from ._client import get_client
|
||||
|
||||
|
||||
def fetch_timeline(limit: int = 10) -> TimelineResult:
|
||||
"""Fetch the authenticated user's timeline."""
|
||||
try:
|
||||
client = get_client()
|
||||
timeline = client.get_timeline(limit=limit)
|
||||
|
||||
posts = []
|
||||
for feed_view in timeline.feed:
|
||||
post = feed_view.post
|
||||
posts.append(
|
||||
Post(
|
||||
uri=post.uri,
|
||||
cid=post.cid,
|
||||
text=post.record.text if hasattr(post.record, "text") else "",
|
||||
author=post.author.handle,
|
||||
created_at=post.record.created_at,
|
||||
likes=post.like_count or 0,
|
||||
reposts=post.repost_count or 0,
|
||||
replies=post.reply_count or 0,
|
||||
)
|
||||
)
|
||||
|
||||
return TimelineResult(
|
||||
success=True,
|
||||
posts=posts,
|
||||
count=len(posts),
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
return TimelineResult(
|
||||
success=False,
|
||||
posts=[],
|
||||
count=0,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
|
||||
def search_for_posts(query: str, limit: int = 10) -> SearchResult:
|
||||
"""Search for posts containing specific text."""
|
||||
try:
|
||||
client = get_client()
|
||||
search_results = client.app.bsky.feed.search_posts(
|
||||
params={"q": query, "limit": limit}
|
||||
)
|
||||
|
||||
posts = []
|
||||
for post in search_results.posts:
|
||||
posts.append(
|
||||
Post(
|
||||
uri=post.uri,
|
||||
cid=post.cid,
|
||||
text=post.record.text if hasattr(post.record, "text") else "",
|
||||
author=post.author.handle,
|
||||
created_at=post.record.created_at,
|
||||
likes=post.like_count or 0,
|
||||
reposts=post.repost_count or 0,
|
||||
replies=post.reply_count or 0,
|
||||
)
|
||||
)
|
||||
|
||||
return SearchResult(
|
||||
success=True,
|
||||
query=query,
|
||||
posts=posts,
|
||||
count=len(posts),
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
return SearchResult(
|
||||
success=False,
|
||||
query=query,
|
||||
posts=[],
|
||||
count=0,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
|
||||
def fetch_notifications(limit: int = 10) -> NotificationsResult:
|
||||
"""Fetch recent notifications."""
|
||||
try:
|
||||
client = get_client()
|
||||
notifs = client.app.bsky.notification.list_notifications(
|
||||
params={"limit": limit}
|
||||
)
|
||||
|
||||
notifications = []
|
||||
for notif in notifs.notifications:
|
||||
notifications.append(
|
||||
Notification(
|
||||
uri=notif.uri,
|
||||
cid=notif.cid,
|
||||
author=notif.author.handle,
|
||||
reason=notif.reason,
|
||||
is_read=notif.is_read,
|
||||
indexed_at=notif.indexed_at,
|
||||
)
|
||||
)
|
||||
|
||||
return NotificationsResult(
|
||||
success=True,
|
||||
notifications=notifications,
|
||||
count=len(notifications),
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
return NotificationsResult(
|
||||
success=False,
|
||||
notifications=[],
|
||||
count=0,
|
||||
error=str(e),
|
||||
)
|
||||
108
examples/atproto_mcp/src/atproto_mcp/_atproto/_social.py
Normal file
108
examples/atproto_mcp/src/atproto_mcp/_atproto/_social.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
"""Social actions like follow, like, and repost."""
|
||||
|
||||
from atproto_mcp.types import FollowResult, LikeResult, RepostResult
|
||||
|
||||
from ._client import get_client
|
||||
|
||||
|
||||
def follow_user_by_handle(handle: str) -> FollowResult:
|
||||
"""Follow a user by their handle."""
|
||||
try:
|
||||
client = get_client()
|
||||
# Search for the user to get their DID
|
||||
results = client.app.bsky.actor.search_actors(params={"q": handle, "limit": 1})
|
||||
if not results.actors:
|
||||
return FollowResult(
|
||||
success=False,
|
||||
did=None,
|
||||
handle=None,
|
||||
uri=None,
|
||||
error=f"User @{handle} not found",
|
||||
)
|
||||
|
||||
actor = results.actors[0]
|
||||
# Create the follow
|
||||
follow = client.follow(actor.did)
|
||||
return FollowResult(
|
||||
success=True,
|
||||
did=actor.did,
|
||||
handle=actor.handle,
|
||||
uri=follow.uri,
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
return FollowResult(
|
||||
success=False,
|
||||
did=None,
|
||||
handle=None,
|
||||
uri=None,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
|
||||
def like_post_by_uri(uri: str) -> LikeResult:
|
||||
"""Like a post by its AT URI."""
|
||||
try:
|
||||
client = get_client()
|
||||
# Parse the URI to get the components
|
||||
# URI format: at://did:plc:xxx/app.bsky.feed.post/yyy
|
||||
parts = uri.replace("at://", "").split("/")
|
||||
if len(parts) != 3 or parts[1] != "app.bsky.feed.post":
|
||||
raise ValueError("Invalid post URI format")
|
||||
|
||||
# Get the post to retrieve its CID
|
||||
post = client.app.bsky.feed.get_posts(params={"uris": [uri]})
|
||||
if not post.posts:
|
||||
raise ValueError("Post not found")
|
||||
|
||||
cid = post.posts[0].cid
|
||||
|
||||
# Now like the post with both URI and CID
|
||||
like = client.like(uri, cid)
|
||||
return LikeResult(
|
||||
success=True,
|
||||
liked_uri=uri,
|
||||
like_uri=like.uri,
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
return LikeResult(
|
||||
success=False,
|
||||
liked_uri=None,
|
||||
like_uri=None,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
|
||||
def repost_by_uri(uri: str) -> RepostResult:
|
||||
"""Repost a post by its AT URI."""
|
||||
try:
|
||||
client = get_client()
|
||||
# Parse the URI to get the components
|
||||
# URI format: at://did:plc:xxx/app.bsky.feed.post/yyy
|
||||
parts = uri.replace("at://", "").split("/")
|
||||
if len(parts) != 3 or parts[1] != "app.bsky.feed.post":
|
||||
raise ValueError("Invalid post URI format")
|
||||
|
||||
# Get the post to retrieve its CID
|
||||
post = client.app.bsky.feed.get_posts(params={"uris": [uri]})
|
||||
if not post.posts:
|
||||
raise ValueError("Post not found")
|
||||
|
||||
cid = post.posts[0].cid
|
||||
|
||||
# Now repost with both URI and CID
|
||||
repost = client.repost(uri, cid)
|
||||
return RepostResult(
|
||||
success=True,
|
||||
reposted_uri=uri,
|
||||
repost_uri=repost.uri,
|
||||
error=None,
|
||||
)
|
||||
except Exception as e:
|
||||
return RepostResult(
|
||||
success=False,
|
||||
reposted_uri=None,
|
||||
repost_uri=None,
|
||||
error=str(e),
|
||||
)
|
||||
0
examples/atproto_mcp/src/atproto_mcp/py.typed
Normal file
0
examples/atproto_mcp/src/atproto_mcp/py.typed
Normal file
154
examples/atproto_mcp/src/atproto_mcp/server.py
Normal file
154
examples/atproto_mcp/src/atproto_mcp/server.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
"""ATProto MCP Server - Public API exposing Bluesky tools and resources."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from atproto_mcp import _atproto
|
||||
from atproto_mcp.settings import settings
|
||||
from atproto_mcp.types import (
|
||||
FollowResult,
|
||||
LikeResult,
|
||||
NotificationsResult,
|
||||
PostResult,
|
||||
ProfileInfo,
|
||||
RepostResult,
|
||||
RichTextLink,
|
||||
RichTextMention,
|
||||
SearchResult,
|
||||
ThreadPost,
|
||||
ThreadResult,
|
||||
TimelineResult,
|
||||
)
|
||||
from fastmcp import FastMCP
|
||||
|
||||
atproto_mcp = FastMCP(
|
||||
"ATProto MCP Server",
|
||||
dependencies=[
|
||||
"atproto_mcp@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/atproto_mcp",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Resources - read-only operations
|
||||
@atproto_mcp.resource("atproto://profile/status")
|
||||
def atproto_status() -> ProfileInfo:
|
||||
"""Check the status of the ATProto connection and current user profile."""
|
||||
return _atproto.get_profile_info()
|
||||
|
||||
|
||||
@atproto_mcp.resource("atproto://timeline")
|
||||
def get_timeline() -> TimelineResult:
|
||||
"""Get the authenticated user's timeline feed."""
|
||||
return _atproto.fetch_timeline(settings.atproto_timeline_default_limit)
|
||||
|
||||
|
||||
@atproto_mcp.resource("atproto://notifications")
|
||||
def get_notifications() -> NotificationsResult:
|
||||
"""Get recent notifications for the authenticated user."""
|
||||
return _atproto.fetch_notifications(settings.atproto_notifications_default_limit)
|
||||
|
||||
|
||||
# Tools - actions that modify state
|
||||
@atproto_mcp.tool
|
||||
def post(
|
||||
text: Annotated[
|
||||
str, Field(max_length=300, description="The text content of the post")
|
||||
],
|
||||
images: Annotated[
|
||||
list[str] | None,
|
||||
Field(max_length=4, description="URLs of images to attach (max 4)"),
|
||||
] = None,
|
||||
image_alts: Annotated[
|
||||
list[str] | None, Field(description="Alt text for each image")
|
||||
] = None,
|
||||
links: Annotated[
|
||||
list[RichTextLink] | None, Field(description="Links to embed in the text")
|
||||
] = None,
|
||||
mentions: Annotated[
|
||||
list[RichTextMention] | None, Field(description="User mentions to embed")
|
||||
] = None,
|
||||
reply_to: Annotated[
|
||||
str | None, Field(description="AT URI of post to reply to")
|
||||
] = None,
|
||||
reply_root: Annotated[
|
||||
str | None, Field(description="AT URI of thread root (defaults to reply_to)")
|
||||
] = None,
|
||||
quote: Annotated[str | None, Field(description="AT URI of post to quote")] = None,
|
||||
) -> PostResult:
|
||||
"""Create a post with optional rich features like images, quotes, replies, and rich text.
|
||||
|
||||
Examples:
|
||||
- Simple post: post("Hello world!")
|
||||
- With image: post("Check this out!", images=["https://example.com/img.jpg"])
|
||||
- Reply: post("I agree!", reply_to="at://did/app.bsky.feed.post/123")
|
||||
- Quote: post("Great point!", quote="at://did/app.bsky.feed.post/456")
|
||||
- Rich text: post("Check out example.com", links=[{"text": "example.com", "url": "https://example.com"}])
|
||||
"""
|
||||
return _atproto.create_post(
|
||||
text, images, image_alts, links, mentions, reply_to, reply_root, quote
|
||||
)
|
||||
|
||||
|
||||
@atproto_mcp.tool
|
||||
def follow(
|
||||
handle: Annotated[
|
||||
str,
|
||||
Field(
|
||||
description="The handle of the user to follow (e.g., 'user.bsky.social')"
|
||||
),
|
||||
],
|
||||
) -> FollowResult:
|
||||
"""Follow a user by their handle."""
|
||||
return _atproto.follow_user_by_handle(handle)
|
||||
|
||||
|
||||
@atproto_mcp.tool
|
||||
def like(
|
||||
uri: Annotated[str, Field(description="The AT URI of the post to like")],
|
||||
) -> LikeResult:
|
||||
"""Like a post by its AT URI."""
|
||||
return _atproto.like_post_by_uri(uri)
|
||||
|
||||
|
||||
@atproto_mcp.tool
|
||||
def repost(
|
||||
uri: Annotated[str, Field(description="The AT URI of the post to repost")],
|
||||
) -> RepostResult:
|
||||
"""Repost a post by its AT URI."""
|
||||
return _atproto.repost_by_uri(uri)
|
||||
|
||||
|
||||
@atproto_mcp.tool
|
||||
def search(
|
||||
query: Annotated[str, Field(description="Search query for posts")],
|
||||
limit: Annotated[
|
||||
int, Field(ge=1, le=100, description="Number of results to return")
|
||||
] = settings.atproto_search_default_limit,
|
||||
) -> SearchResult:
|
||||
"""Search for posts containing specific text."""
|
||||
return _atproto.search_for_posts(query, limit)
|
||||
|
||||
|
||||
@atproto_mcp.tool
|
||||
def create_thread(
|
||||
posts: Annotated[
|
||||
list[ThreadPost],
|
||||
Field(
|
||||
description="List of posts to create as a thread. Each post can have text, images, links, mentions, and quotes."
|
||||
),
|
||||
],
|
||||
) -> ThreadResult:
|
||||
"""Create a thread of posts with automatic linking.
|
||||
|
||||
The first post becomes the root of the thread, and each subsequent post
|
||||
replies to the previous one, maintaining the thread structure.
|
||||
|
||||
Example:
|
||||
create_thread([
|
||||
{"text": "Starting a thread about Python 🧵"},
|
||||
{"text": "Python is great for rapid development"},
|
||||
{"text": "And the ecosystem is amazing!", "images": ["https://example.com/python.jpg"]}
|
||||
])
|
||||
"""
|
||||
return _atproto.create_thread(posts)
|
||||
17
examples/atproto_mcp/src/atproto_mcp/settings.py
Normal file
17
examples/atproto_mcp/src/atproto_mcp/settings.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=[".env"], extra="ignore")
|
||||
|
||||
atproto_handle: str = Field(default=...)
|
||||
atproto_password: str = Field(default=...)
|
||||
atproto_pds_url: str = Field(default="https://bsky.social")
|
||||
|
||||
atproto_notifications_default_limit: int = Field(default=10)
|
||||
atproto_timeline_default_limit: int = Field(default=10)
|
||||
atproto_search_default_limit: int = Field(default=10)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue