Alexa+ MCP server¶
naf/alexa prepares a NAF MCP endpoint for an Alexa+ add-on. It reuses the existing MCP
transport and tool registry, OAuth authorization server, database migrations and console.
It supplies configuration defaults, separate OAuth client setup, local diagnostics and an
optional Amazon manifest export. Application tools and business rules stay in the host.
The example exposes one public service_status tool, authenticated with a service token.
It needs no customer account linking. The NAF setup and HTTP exchange are tested against
published packages by this documentation's example runner.
Amazon's MCP Toolkit overview currently lists availability in the United States. Amazon registration requires access to private developer tooling and a confirmed way to provision the service credentials. Read Connect to Alexa+ before planning deployment: the public Amazon docs leave the service-secret provisioning step unspecified, so this guide cannot yet establish an end-to-end Alexa connection. The plugin does not register or publish an add-on at Amazon. This integration exposes tools and data; MCP Apps visual resources and category SDK APIs are outside its scope.
Required packages
In an existing NAF application:
composer require naf/alexa
Also installs these transitive dependencies: naf/auth, naf/cli, naf/database, naf/form, naf/mcp, naf/oauth-server, naf/session.
Prepare a host¶
Use PHP 8.3+, mbstring, readline, PDO and pdo_sqlite for this example. This guide
requires naf/alexa 0.1.0+, naf/mcp 0.2.5+ and naf/oauth-server 0.2.4+.
Create a host application; only its public/ directory is the document root:
composer create-project naf/app alexa-demo
cd alexa-demo
composer require naf/alexa
mkdir -p storage
For an existing application, run the last two commands from its root instead.
Composer installs naf/mcp, naf/oauth-server, naf/database and naf/cli, with their
dependencies. Plugin boot order is automatic on the required framework 0.2.8+. Do not copy
vendor migrations or maintain a separate HTTP dispatcher.
Set these application environment values in .env (or the deployment environment).
If .env.local exists, set them there because that file replaces .env:
PUBLIC_URL=https://tools.example.com
ALEXA_MCP_RESOURCE=https://tools.example.com/mcp
ALEXA_NAME=Example tools
Replace the example domain with the host's public HTTPS origin. The canonical resource must
be that exact origin plus /mcp, without a trailing slash, query or fragment. This first
profile uses the authorization server in the same host. Keep these URLs stable and ensure
the proxy sends requests to the right application.
In a new minimal host, this is a complete app/config.php; extend existing configuration
instead if your application already has a database or authentication settings:
<?php
declare(strict_types=1);
return [
'database' => [
'driver' => 'sqlite',
'database' => BASE_PATH . '/storage/alexa.sqlite',
],
'mcp' => ['transport' => ['streaming' => false]],
];
The plugin derives its OAuth issuer, MCP resource metadata and expected audience from the
environment values. Authentication uses OAuth and remains enabled. Service discovery uses
mcp:service; account linking is disabled until configured. POST responses default to JSON,
which is a Streamable HTTP response mode. Set mcp:transport:streaming to true for SSE progress
and final results, or false to use JSON. See MCP tools for streaming tools,
client Accept headers and proxy buffering.
Register a public tool¶
Create app/Mcp/ServiceStatus.php:
<?php
declare(strict_types=1);
namespace App\Mcp;
use Naf\MCP\Tools\ToolInterface;
final class ServiceStatus implements ToolInterface
{
public function name(): string
{
return 'service_status';
}
public function description(): string
{
return 'Check whether the example service is available. No personal data is returned.';
}
public function inputSchema(): array
{
return ['type' => 'object', 'properties' => [], 'additionalProperties' => false];
}
public function handle(array $args): mixed
{
return ['status' => 'available'];
}
}
For this new application, replace root bootstrap.php with this complete file.
In an existing host, add the imports and tool registration after Composer autoloading and
before app()->run(), preserving its other registrations:
<?php
declare(strict_types=1);
use App\Mcp\ServiceStatus;
use function Naf\app;
use function Naf\MCP\tool;
define('BASE_PATH', __DIR__);
require __DIR__ . '/vendor/autoload.php';
tool()->register(new ServiceStatus());
app()->run();
A service token can call this tool because it returns only public information. Implement
personal tools with UserToolInterface and ScopedToolInterface; never expose personal data
through an unscoped service tool.
Set up and check¶
Run from the host root against the configured database:
vendor/bin/naf alexa:setup --migrate
vendor/bin/naf alexa:doctor --server-only
--migrate opts into the existing OAuth migration only. It does not run unrelated application
migrations. Omit it when your deployment already applies OAuth migrations through db:migrate up.
Setup registers a confidential client limited to client_credentials, mcp:service and this
MCP audience. It prints the client id and secret once. Keep them in private credential storage
for Amazon service authentication. Setup does not
send them to Amazon. Keep the secret out of source control, query strings and the manifest.
Repeated setup reuses a matching registration without rotating its secret or creating another
client. If registration and configuration differ, review the existing registration before
revoking and recreating it. Use oauth:client:rotate-secret for a deliberate secret rotation.
Doctor runs local diagnostics and exits; it does not start or stop an HTTP server.
It returns exit code 0 when its selected local checks pass, or 1 on failure. It checks
configuration, OAuth tables, discovery capabilities, client separation and tool scopes.
--server-only limits the checks to server configuration and skips the store listing.
Doctor without that option also validates the local
listing metadata. These checks do not verify public reachability, hosted image dimensions,
latency, login/consent UX, Amazon account access or certification.
Verify HTTP¶
For local protocol checks, pass the existing web entry point as the development server's
router. PHP 8.3 otherwise treats some /.well-known/ paths as missing static files:
php -S 127.0.0.1:8000 -t public public/index.php
Leave this terminal running and stop it with Ctrl+C. To connect Alexa, expose the application through a public HTTPS host or tunnel and use that origin in both environment values above. Restart the development server if you change the environment values. A local HTTP check alone does not establish public reachability.
For public hosting, route these paths through the NAF front controller as described in Deployment.
Public discovery documents must return JSON:
curl https://tools.example.com/.well-known/oauth-protected-resource/mcp
curl https://tools.example.com/.well-known/oauth-protected-resource
curl https://tools.example.com/.well-known/oauth-authorization-server
PRM names https://tools.example.com/mcp and the authorization server origin. The authorization
metadata advertises client_credentials, authorization code, refresh and S256. No signing key
is needed for OAuth-only usage. An unauthenticated MCP request returns 401 without a
WWW-Authenticate header, as required by the Alexa Toolkit.
Verify the service credentials independently of Amazon. Replace the client id below with the service id from setup. Curl prompts for its secret as the HTTP Basic password:
ALEXA_SERVICE_CLIENT_ID='YOUR_SERVICE_CLIENT_ID'
curl --fail-with-body \
--user "$ALEXA_SERVICE_CLIENT_ID" \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=mcp:service' \
--data-urlencode 'resource=https://tools.example.com/mcp' \
'https://tools.example.com/oauth/token'
Expect an access token with token_type: "Bearer", scope: "mcp:service" and no refresh
token. Treat the returned access token as a credential too. Request another service token
on expiry. Send the bearer in the Authorization header on every MCP request, with both
accepted response media types:
POST /mcp HTTP/1.1
Authorization: Bearer YOUR_SERVICE_ACCESS_TOKEN
Content-Type: application/json
Accept: application/json, text/event-stream
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"Example test","version":"1.0"}}}
Send the negotiated MCP-Protocol-Version header on subsequent requests. Send
notifications/initialized, then tools/list and tools/call for service_status.
The result contains structuredContent: {"status":"available"} and a JSON text content block.
The server supports the documented 2025-03-26 client lifecycle as well as 2025-11-25.
Connect to Alexa+¶
Follow these steps after the public discovery and service-token checks pass. A successful local exchange proves the NAF endpoint works; Amazon credential provisioning and simulator testing remain separate steps.
Obtain Amazon tooling access¶
Complete Amazon's development-environment setup.
It requires Node.js 24+, an Alexa developer account and an AWS account admitted to Amazon's
developer-tools role. The CLI is distributed through Amazon's private CodeArtifact registry;
npm install -g @alexa-ai/cli requires that registry setup first. Contact your Amazon
onboarding representative if your account has not been granted access. Hosting this PHP
application on AWS is not required.
After the documented installation, check the installed CLI and authenticate:
alexa-ai --version
alexa-ai new mcp --help
alexa-ai configure
configure signs the developer into Amazon through Login with Amazon. It does not install
the NAF service client's credentials. Keep these identities separate:
| Credentials | Purpose | Configured by |
|---|---|---|
| Amazon developer login | Manage add-ons at Amazon | alexa-ai configure |
| NAF service client | MCP discovery and public tools using mcp:service |
alexa:setup, then Amazon service provisioning below |
| Optional NAF account-linking client | Act for a customer using user scopes | Separate setup under Add account linking |
Create the add-on project¶
Run this in a directory for your Amazon add-on projects, separate from the PHP application. Replace the URL and service client id:
alexa-ai new mcp \
--name "NAF Demo" \
--locale en-US \
--mcp-server-url "https://tools.example.com/mcp" \
--requires-auth \
--auth-client-id "YOUR_SERVICE_CLIENT_ID" \
--auth-scopes "mcp:service"
The CLI reference documents these options. Check them against the installed version's help. This command creates local files; it does not register credentials with Amazon. If asked about customer account linking, leave it disabled for the public status tool.
Change into the generated project directory reported by the CLI. Complete its
addon-package/addon.json with descriptions, three or four example phrases, privacy and
terms URLs, and the required images. See Prepare the Amazon package
for the listing fields and optional NAF export. Keep all OAuth secrets outside this package.
Provision the service client at Amazon¶
Amazon's authentication guide requires Alexa to obtain a service token with these settings:
| Setting | Value for this host |
|---|---|
| MCP endpoint / canonical resource | https://tools.example.com/mcp |
| Authorization server | https://tools.example.com |
| Token endpoint | https://tools.example.com/oauth/token |
| Grant type | client_credentials |
| Client authentication | HTTP Basic with the service client id and secret from alexa:setup |
| Scope | mcp:service |
Token-request resource |
https://tools.example.com/mcp |
Amazon provisioning is not yet verified. As of 2026-10-10, the public authentication
guide describes the token exchange, but neither the CLI reference nor the
Add-on API reference
identifies a service-secret upload command, API operation or console field. The private CLI
was not available for verification. There is therefore no confirmed secret-entry instruction
in this guide yet; the new mcp command above is not sufficient to connect this protected host.
Ask your assigned Alexa+ Solutions Architect for the service-credential provisioning procedure through Amazon developer support. Provide the settings above and your CLI version, without including the secret. A specific question to resolve is:
How do I provision a confidential OAuth client for Tier 1 MCP service authentication? Which supported CLI command, API operation or Developer Hub field stores its client secret for the client_credentials grant, and at which step before the first authenticated deployment?
Use the credential channel Amazon confirms for that purpose. The masked prompt and
ALEXA_CLIENT_SECRET documented for configure-account-linking belong to the separate user
client; they do not establish a service-secret provisioning mechanism. Keep service and user
clients separate, and keep MCP authentication enabled while resolving this step.
Deploy and verify the connection¶
Continue only after Amazon confirms the service credential provisioning and the add-on listing is complete. From the generated Amazon project directory, run:
alexa-ai deploy
alexa-ai status
Deployment creates or updates the development-stage add-on and reports its add-on id.
Open it in Amazon's web simulator
and ask, for example, "Is the NAF demo service available?" Use your host's request tracing
to verify the token request and a real service_status invocation; confirm the reply
reflects {"status":"available"}. Keep tracing free of Authorization headers, tokens and
client secrets. A successful deployment status alone does not prove a tool was called.
Re-deploy after tool/schema or authentication metadata changes: Amazon refreshes its cached configuration on deployment. Test public latency and any SSE flushing through the actual proxy before certification; the QuickStart specifies a round trip below 500 ms.
Add account linking¶
This optional extension is for personal tools, after service authentication works. Use your
application's existing naf/auth provider and login page.
Obtain the exact HTTPS callback URIs from Amazon's account-linking setup; do not guess them.
Extend the host configuration with:
'oauth_server' => ['login_route' => '/login'],
'alexa' => [
'account_linking' => true,
'redirect_uris' => ['https://AMAZON_SUPPLIED_HOST/EXACT_CALLBACK'],
'user_scopes' => ['mcp:tools'],
],
The default mcp:tools scope requires the current account's mcp:tools permission. Grant that
through your existing identity/provider, or define narrower application scopes and permissions
in oauth_server:scopes. If changing the user scope list, also set the same list in
mcp:oauth:scopes_supported; numeric configuration arrays merge by position.
Run setup again. It adds a second confidential client limited to authorization code and refresh grants, exact callback URIs, user scopes and this audience. It never gives that client the service grant. Configure this separate client in Amazon's account-linking settings.
Amazon's account-linking guide documents this command for the user client:
alexa-ai configure-account-linking \
--addon-id "YOUR_ADDON_ID" \
--stage development \
--client-id "YOUR_ACCOUNT_LINKING_CLIENT_ID"
Enter the account-linking client's secret at the masked prompt. That guide also documents
ALEXA_CLIENT_SECRET as an alternative for this user-client configuration. The general CLI
reference instead shows a --config-file interface; inspect
alexa-ai configure-account-linking --help and follow the instructions matching your installed
version before proceeding. These public instructions have not been checked against the
private CLI. Neither variant is a verified way to provision the service client's secret.
Register all exact redirect URIs supplied by Amazon, review the discovered endpoints and user scopes, then deploy again. Test a personal tool in the simulator: complete your host's login and consent, verify the caller's identity and permissions, and test refresh and revocation. See OAuth authorization server for the host login and consent flow.
A personal tool declares its user scope. Inside its handler, obtain the token's user:
use function Naf\OAuth\Server\token;
$account = token()->user();
// Pass this verified account to your existing application service.
Do not read the browser session as the Alexa caller. The OAuth resource server reloads the token's account and rechecks current permissions. Service discovery can list personal tool metadata, but invoking one without a linked account returns HTTP 401. A linked account lacking the required scope/permission receives 403. Authorization code exchange requires PKCE S256; the requested resource is checked against the code's authorized audience. Refresh keeps that audience without requiring the resource parameter again.
Prepare the Amazon package¶
This is the listing reference for the add-on project. You can edit the CLI-generated package directly or prepare listing metadata in NAF and export it.
Supply alexa:listing in host configuration using the en-US locale entry from the
QuickStart's addon.json schema.
The plugin's export validates:
| Field | Requirement |
|---|---|
name.value |
1–30 characters |
shortDescription, fullDescription |
1–123 and 1–4000 characters |
examplePhrases |
3–4 nonempty phrases, each at most 200 characters |
privacyAndCompliance URLs |
HTTPS privacy policy and terms of use |
mediaAssets.icons.light |
Exactly 64, 72, 88, 126, 180 and 241 pixel square entries |
mediaAssets.icons.dark |
Optional; the same six sizes when supplied |
mediaAssets.carouselImages |
At least one 600x900 image with alt text |
mediaAssets.bannerImages |
Optional 1200x600 images with alt text |
Use {size, uri} objects for icons and {size, uri, altText} objects for other images.
Image URIs use HTTPS and PNG, JPG, JPEG or WEBP paths. Alt text is at most 250 characters.
The validator checks declared metadata, not remote image bytes or ownership.
Export to a new file and review it alongside the CLI-generated package:
vendor/bin/naf alexa:setup --manifest storage/alexa-addon.json
vendor/bin/naf alexa:doctor
The file uses manifest version 1.0, US distribution, the en-US listing and an MCP HTTPS
integration pointing at the canonical resource. It contains no OAuth credentials. Setup
refuses to overwrite an existing file. Transfer the reviewed metadata into the CLI project's
addon-package/addon.json before deployment. Follow the separate
service credential provisioning and optional
account-linking steps; the export does not transmit credentials.
After the connection and simulator checks pass, follow Amazon's certification procedure.
If the result is different¶
- 400, 406 or 415 before MCP handles a call: check Content-Type, Accept, protocol version and that
csrf_exempt_routes:mcp_server_rpchas not been disabled by host configuration. - 401 with a service token on a personal tool: account linking is needed; keep the service and user clients separate.
- 403 after linking: check the token's user scopes and the reloaded account's permissions.
invalid_granton code exchange: check PKCE verifier, exact callback and canonical resource. A code attempted with mismatched bindings is spent; start authorization again.- Buffered SSE: inspect reverse-proxy/CDN buffering and compression; verify that the supported framework emitter is installed.
- Doctor passes but Amazon fails: verify public HTTPS/discovery, credentials in the correct authentication tier, actual images, simulator login/consent and developer account access.
- The service secret has no documented input: resolve Amazon service provisioning with your onboarding contact; the account-linking secret prompt configures a different OAuth client.
- CLI installation returns 404 or registry authentication fails: check private CodeArtifact access and its registry login. The public npm registry does not provide the Alexa AI CLI.
- Account-linking flags are rejected: compare the installed command's help with the account-linking guide and CLI reference; their documented interfaces currently differ.