About Falcon MCP Server
Falcon MCP Server is an MCP server published by falconry in the Developer Tools category: uncomplicated MCP endpoint for Falcon apps. Falcon is the only dependency.
The repository has 2 stars and 0 forks, with the last commit 2 days ago. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/falconry/falcon-mcp-serverThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Falcon MCP Server
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository yesterday. Heuristics on the source, not an audit.
- ✓README presentThe repository ships documentation.
- ✓License declaredUsable under a stated license.
- ✓Tests presentA test suite exists in the repository.
- !Dependencies pinnedNo lockfile. Installs can pull different code over time.
- ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
- !Scoped permissionsDoesn't declare a permission scope. Assume it can do anything its process can.
Releases
README
falcon-mcp-server
falcon-mcp-server is a lightweight library for building MCP server endpoints
on top of your Falcon application.
Warning:
falcon-mcp-serveris a fast-moving project in its early stages. Caveat emptor!
Note:
falcon-mcp-serverwas upgraded to the current version of the MCP standard with substantial help from LLMs, under human supervision.
Table of Contents
Installation
Install from PyPI:
pip install falcon-mcp-server[serve]
This is a fast-moving project in its early stages where new versions are not
always immediately released to PyPI. To install directly from the main branch:
pip install git+https://github.com/falconry/falcon-mcp-server
Example Usage
See the example in examples/example.py, which exposes a tool, a prompt
and a resource.
Change dir to examples/, and run with Uvicorn:
uvicorn --log-config logging.yaml --factory example:mcp.create_app
Connect with any AI agent or MCP inspector supporting the
Streamable HTTP
transport as of the 2026-07-28 revision of MCP. Earlier protocol revisions
(the ones relying on the initialize handshake and sessions) are not
supported.
For instance, assuming you have both
fast-agent
(pip install fast-agent-mcp) and Ollama installed on
your system, you can run it against the previously started server (see above)
as:
fast-agent go --url http://localhost:8000/mcp --model generic.granite4:3b --message "What is the current temperature in London?"
Omit --message to run interactively.
Feel free to pull and use a different ollama model!
The server can introduce itself to clients under your own name, and provide instructions that clients may pass on to the LLM alongside the tools:
mcp = falcon_mcp_server.MCP(
name='weather-example',
title='Weather Example',
version='1.2.3',
instructions='Temperatures are only known for New York and London.',
)
A tool or prompt receives the context of the request via the parameter that it
annotates with Context, if any. The context carries the Falcon request and,
if you add the end point to your app on a templated route, the values of its
fields:
import falcon.asgi
from falcon_mcp_server import Context
async def get_quota(context: Context) -> str:
account = context.path_params['account']
token = context.req.auth
...
mcp.add_tool(get_quota, annotations={'readOnlyHint': True})
app = falcon.asgi.App()
app.add_route('/{account:int}/mcp', mcp)
The annotations are hints describing the behavior of a tool to clients. The
dictionary is typed as ToolAnnotations, so a type checker catches a misspelt
or non-boolean hint.
The tools exposed to a request can be narrowed down with a filter. A tool that is filtered out is neither listed nor callable:
def tool_filter(context, tool):
if context.req.get_param_as_bool('read_only'):
return bool(tool.annotations.get('readOnlyHint'))
return True
mcp = falcon_mcp_server.MCP(tool_filter=tool_filter)
A tool result that is not a string is rendered as JSON for the model. Pass a serializer to render it otherwise, e.g., as TOON:
# With toon-format, the official implementation:
mcp = falcon_mcp_server.MCP(result_serializer=toon_format.dumps)
# Or with toons, which is implemented in Rust:
mcp = falcon_mcp_server.MCP(result_serializer=toons.dumps)
As with tools, a parameter of the serializer annotated with Context receives
the context of the request. This way, the format can also be chosen via the
URL of the end point, such as /mcp?format=toon:
def serialize(result, context: Context) -> str:
if context.req.get_param('format') == 'toon':
return toon_format.dumps(result)
return json.dumps(result)
mcp = falcon_mcp_server.MCP(result_serializer=serialize)
A tool argument can be mirrored into an HTTP header, e.g., for a reverse proxy
to log or route on without parsing the request body. Annotate the argument
with x-mcp-header in the input schema, as the example does for city, and
clients send its value in the Mcp-Param-City header; a call where the header
and the argument disagree is rejected:
mcp.add_tool(
get_temperature,
input_schema={
'type': 'object',
'properties': {
'city': {'type': 'string', 'x-mcp-header': 'City'},
},
'required': ['city'],
},
)
An exception raised by a tool is logged, and reported to the client as an
internal error without any details. The exceptions are Falcon's HTTP errors,
such as falcon.HTTPUnauthorized, which are rendered as usual. To tell the
model what went wrong, so that it can correct itself, raise ToolError:
from falcon_mcp_server import ToolError
async def get_temperature(city: str) -> str:
if city not in temperatures:
raise ToolError(f'Unknown city; try one of: {", ".join(temperatures)}')
...
Besides the fixed contents of add_simple_resource, a resource can be read by
a callable, which is awaited on every read. It returns text (str) or binary
data (bytes), and receives the context in the same way as a tool does:
async def get_usage(context: Context) -> str:
"""Storage and bandwidth used by the account this month."""
account = context.path_params['account']
...
mcp.add_resource('usage://current', 'application/json', get_usage)
A tool can be paired with an
MCP App, i.e., an HTML
widget that the supporting hosts render in place of the plain tool result.
Serve the widget as a ui:// resource, and point the tool to it via meta.
A dict that the tool returns is also sent as structured content, which is
what the widget gets to render:
WIDGET_URI = 'ui://weather-example/temperature.html'
mcp.add_tool(show_temperature, meta={'ui': {'resourceUri': WIDGET_URI}})
mcp.add_resource(WIDGET_URI, 'text/html;profile=mcp-app', read_widget)
A widget that loads scripts, styles or media from elsewhere, or connects to
other origins, declares them in the _meta of its resource; the host builds
the Content Security Policy of the widget from them:
csp = {
'resourceDomains': ['https://cdn.example.com'],
'connectDomains': ['https://api.example.com'],
}
mcp.add_resource(
WIDGET_URI,
'text/html;profile=mcp-app',
read_widget,
meta={'ui': {'csp': csp}},
)
See examples/widget.py and examples/widget.html for the complete
example. It runs in the same way as the first one:
uvicorn --log-config logging.yaml --factory widget:mcp.create_app
A host that runs in a web browser needs its origin to be allowed, see below.
Requests from web browsers (i.e., the ones carrying an Origin header) are
rejected with HTTP 403 unless their origin is explicitly allowed. Optionally,
the same origins can also be granted access via CORS:
mcp = falcon_mcp_server.MCP(allowed_origins=['http://localhost:6274'])
app = mcp.create_app(cors_enable=True)
To require authentication, pass an authenticator, i.e., an async callable that receives the context of a request, and tells whether the request is authenticated; any other request is rejected with HTTP 401. This is also where to check that an access token was issued for this very end point.
Clients can then sign the user in via OAuth if the end point describes itself
with protected resource metadata
(RFC 9728), which names its
authorization server(s). The 401 response points to the metadata, and its
resource is filled in from the request (honoring X-Forwarded-* headers
behind a reverse proxy):
async def authenticate(context: Context) -> bool:
token = context.req.auth
...
mcp = falcon_mcp_server.MCP(
authenticator=authenticate,
resource_metadata={
'authorization_servers': ['https://auth.example.com'],
'scopes_supported': ['mcp'],
},
)
create_app() serves the metadata at
/.well-known/oauth-protected-resource/mcp. On an app of your own, add the
well-known route via the metadata suffix:
app.add_route('/{account:int}/mcp', mcp)
app.add_route(
'/.well-known/oauth-protected-resource/{account:int}/mcp',
mcp,
suffix='metadata',
)
License
falcon-mcp-server is distributed under the terms of the Apache-2.0 license.
README fetched from the repository yesterday. The original is authoritative.