@sendora/mcp 1.8.0 → 1.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -356,7 +356,7 @@ button:focus-visible {
356
356
  }
357
357
 
358
358
  // src/version.ts
359
- var MCP_VERSION = "1.8.0";
359
+ var MCP_VERSION = "1.8.7";
360
360
 
361
361
  // screens/host.ts
362
362
  var PROTOCOL_VERSION = "2026-01-26";
@@ -356,7 +356,7 @@ button:focus-visible {
356
356
  }
357
357
 
358
358
  // src/version.ts
359
- var MCP_VERSION = "1.8.0";
359
+ var MCP_VERSION = "1.8.7";
360
360
 
361
361
  // screens/host.ts
362
362
  var PROTOCOL_VERSION = "2026-01-26";
@@ -1,8 +1,8 @@
1
1
  import { KEYS_PAGE, } from './connection.js';
2
2
  import { INDEX_URI } from './docs.js';
3
3
  import { grantNote, LIVE_NOTE, SIMULATOR_NOTE, WRITES_OFF_NOTE } from './tools/server.js';
4
- const ABOUT = "Sendora is a transactional email service: an HTTP API, SMTP submission, SDKs for TypeScript (@sendora/sdk) and Python (sendora), signed webhooks and a message log with every delivery event. This server carries Sendora's documentation, and with a key, tools for that key's server or account.";
5
- const DOCS = `Use the SDK for the project's language: @sendora/sdk for TypeScript or JavaScript, sendora for Python, the HTTP API otherwise. Before writing code, find the answer with search_docs and read the document with read_doc; ${INDEX_URI} lists them all, and sendora://sdk/skill and sendora://sdk/python/skill hold the rules an agent must keep with each SDK.`;
4
+ const ABOUT = "Sendora is a transactional email service: an HTTP API, SMTP submission, SDKs for TypeScript (@sendora/sdk), Python (sendora) and PHP (sendora/sendora-php, and sendora/sendora-laravel for Laravel), signed webhooks and a message log with every delivery event. This server carries Sendora's documentation, and with a key, tools for that key's server or account.";
5
+ const DOCS = `Use the SDK for the project's language: @sendora/sdk for TypeScript or JavaScript, sendora for Python, sendora/sendora-php for PHP, or sendora/sendora-laravel in a Laravel app, which installs it; the HTTP API otherwise. Before writing code, find the answer with search_docs and read the document with read_doc; ${INDEX_URI} lists them all, and sendora://sdk/skill, sendora://sdk/python/skill and sendora://sdk/php/skill hold the rules an agent must keep with each SDK.`;
6
6
  const KEYS = 'Never print or repeat a Sendora key, and never write one into a file under version control; keys belong in the environment.';
7
7
  const LIVE_DASHBOARD = `A person makes and revokes this server's keys, and makes and deletes its webhooks, in the dashboard, ${KEYS_PAGE}, with changes on or off, since a new key or signing secret must not reach this conversation.`;
8
8
  const ACCOUNT_DASHBOARD = `A server and a key are made in the dashboard, ${KEYS_PAGE}, since a new key is a secret this conversation must not see; a live server is deleted there too.`;
package/dist/prompts.js CHANGED
@@ -18,13 +18,13 @@ export function registerPrompts(server) {
18
18
  framework: z
19
19
  .string()
20
20
  .optional()
21
- .describe('The web framework, such as Express, Next.js, Django or FastAPI.'),
21
+ .describe('The web framework, such as Express, Next.js, Django, FastAPI or Laravel.'),
22
22
  }),
23
23
  }, ({ framework }) => asking([
24
24
  `Set up Sendora for transactional email in this project, which uses ${forFramework(framework)}.`,
25
25
  '',
26
- "1. Read sendora://docs/quickstart and the skill for the project's SDK, sendora://sdk/skill for TypeScript or sendora://sdk/python/skill for Python, with read_doc before writing any code, and follow the skill's rules.",
27
- "2. Install the SDK for the project's language, @sendora/sdk for TypeScript or JavaScript or sendora for Python, and construct the client once at startup from SENDORA_API_TOKEN in the environment; in any other language, call the HTTP API with that key. Never write a key into a file under version control, and never print one.",
26
+ "1. Read sendora://docs/quickstart and the skill for the project's SDK, sendora://sdk/skill for TypeScript, sendora://sdk/python/skill for Python or sendora://sdk/php/skill for PHP, with read_doc before writing any code, and follow the skill's rules. In a Laravel app, read sendora://sdk/laravel/readme too.",
27
+ "2. Install the SDK for the project's language, @sendora/sdk for TypeScript or JavaScript, sendora for Python, sendora/sendora-php for PHP or sendora/sendora-laravel in a Laravel app, and construct the client once at startup from SENDORA_API_TOKEN in the environment; in any other language, call the HTTP API with that key. Never write a key into a file under version control, and never print one.",
28
28
  '3. Add a function that sends one message, with the idempotency the SDK gives every send, or an Idempotency-Key header over HTTP.',
29
29
  '4. If a test server key is connected here, send a test to delivered@simulator.sendora.se and to hardbounce@simulator.sendora.se, and show me how each ended; otherwise tell me how to connect one (sendora://docs/test-servers).',
30
30
  '5. Add a webhook endpoint that verifies every delivery before it parses it, as sendora://docs/webhooks shows.',
@@ -48,11 +48,11 @@ export function registerPrompts(server) {
48
48
  framework: z
49
49
  .string()
50
50
  .optional()
51
- .describe('The web framework, such as Express, Next.js, Django or FastAPI.'),
51
+ .describe('The web framework, such as Express, Next.js, Django, FastAPI or Laravel.'),
52
52
  }),
53
53
  }, ({ framework }) => asking([
54
54
  `Make this project's Sendora webhook endpoint in ${forFramework(framework)} verify every delivery.`,
55
55
  '',
56
- "Read sendora://docs/webhooks first. Before anything parses the body, verify it with the SDK for the project's language: in TypeScript verifyWebhook with the raw body and the Sendora-Signature header, or webhooks.receive with a Fetch Request; in Python verify_webhook with the raw body and the header; in any other language, the recipe in sendora://docs/webhooks. Answer 2xx quickly and do the work afterwards; key the handling on the event id, since deliveries arrive at least once. Keep the secret in the environment. Then test it: if a test server key is connected here, replay a delivery and check that the endpoint accepted it.",
56
+ "Read sendora://docs/webhooks first. Before anything parses the body, verify it with the SDK for the project's language: in TypeScript verifyWebhook with the raw body and the Sendora-Signature header, or webhooks.receive with a Fetch Request; in Python verify_webhook with the raw body and the header; in PHP WebhookVerifier::verify() with the raw body, the header and the secret; in a Laravel app the package's route, which checks each signature and fires a Laravel event; in any other language, the recipe in sendora://docs/webhooks. Answer 2xx quickly and do the work afterwards; key the handling on the event id, since deliveries arrive at least once. Keep the secret in the environment. Then test it: if a test server key is connected here, replay a delivery and check that the endpoint accepted it.",
57
57
  ].join('\n')));
58
58
  }
package/dist/search.js CHANGED
@@ -28,17 +28,20 @@ function singular(word) {
28
28
  }
29
29
  /**
30
30
  * The terms of a text: its words lower-cased and singular, the words of a
31
- * question that carry nothing left out, and a word joined by underscores also
32
- * as its parts, so `recipient_suppressed` is found by either word.
31
+ * question that carry nothing left out, and a word joined by underscores or
32
+ * written in camelCase also as its parts, so `recipient_suppressed` is found
33
+ * by either word, and so is `idempotencyKey`.
33
34
  */
34
35
  export function termsOf(text) {
35
36
  return text
36
- .toLowerCase()
37
37
  .split(/[^\p{L}\p{N}_]+/u)
38
- .filter((word) => word !== '' && !STOPWORDS.has(word))
39
- .flatMap((word) => {
40
- const parts = word.split('_').filter((part) => part !== '' && !STOPWORDS.has(part));
41
- return parts.length > 1 ? [word, ...parts] : [word];
38
+ .map((word) => ({ word: word.toLowerCase(), parts: word.split(/_|(?<=\p{Ll})(?=\p{Lu})/u) }))
39
+ .filter(({ word }) => word !== '' && !STOPWORDS.has(word))
40
+ .flatMap(({ word, parts }) => {
41
+ const kept = parts
42
+ .map((part) => part.toLowerCase())
43
+ .filter((part) => part !== '' && !STOPWORDS.has(part));
44
+ return kept.length > 1 ? [word, ...kept] : [word];
42
45
  })
43
46
  .map(singular);
44
47
  }
@@ -52,7 +52,7 @@ export const docsTools = [
52
52
  operations: [],
53
53
  register: (server, { docs }) => server.registerTool('read_doc', {
54
54
  title: 'Read a Sendora document',
55
- description: `Read one of Sendora's documents whole, as Markdown: a guide (sendora://docs/…), an API operation (sendora://reference/…), the error codes (sendora://errors) or an SDK's README and rules for agents (sendora://sdk/readme and sendora://sdk/skill for TypeScript, sendora://sdk/python/readme and sendora://sdk/python/skill for Python). ${INDEX_URI} lists every document; search_docs finds the right one.`,
55
+ description: `Read one of Sendora's documents whole, as Markdown: a guide (sendora://docs/…), an API operation (sendora://reference/…), the error codes (sendora://errors) or an SDK's README and rules for agents (sendora://sdk/readme and sendora://sdk/skill for TypeScript, sendora://sdk/python/readme and sendora://sdk/python/skill for Python, sendora://sdk/php/readme and sendora://sdk/php/skill for PHP, and sendora://sdk/laravel/readme for Laravel). ${INDEX_URI} lists every document; search_docs finds the right one.`,
56
56
  inputSchema: z.object({
57
57
  uri: z.string().min(1).describe(`A document's address, such as ${INDEX_URI}.`),
58
58
  }),
@@ -200,7 +200,7 @@ export const createWebhookTool = {
200
200
  operations: ['POST /v1/webhooks'],
201
201
  register: (server, { connection, targets }) => registerServerTool(server, targets, 'create_webhook', {
202
202
  title: 'Add a webhook',
203
- description: "Adds a webhook to this test server and answers its signing secret, shown this once: put it in the project's environment, such as SENDORA_WEBHOOK_SECRET in .env, never in a file under version control, and verify every delivery with the SDK: webhooks.receive or verifyWebhook in TypeScript, verify_webhook in Python. The API takes only a public https URL, so a local endpoint needs a tunnel. Events arrive as the simulator plays out each message.",
203
+ description: "Adds a webhook to this test server and answers its signing secret, shown this once: put it in the project's environment, such as SENDORA_WEBHOOK_SECRET in .env, never in a file under version control, and verify every delivery with the SDK: webhooks.receive or verifyWebhook in TypeScript, verify_webhook in Python, WebhookVerifier::verify() in PHP, or in Laravel the package's route. The API takes only a public https URL, so a local endpoint needs a tunnel. Events arrive as the simulator plays out each message.",
204
204
  inputSchema: createWebhookInput,
205
205
  outputSchema: createdWebhookOutput,
206
206
  annotations: {
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** This package's version, held equal to its package.json by a test. */
2
- export const MCP_VERSION = '1.8.0';
2
+ export const MCP_VERSION = '1.8.7';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendora/mcp",
3
- "version": "1.8.0",
3
+ "version": "1.8.7",
4
4
  "description": "The official MCP server for Sendora: its documentation and API for coding agents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,7 +25,7 @@
25
25
  "dependencies": {
26
26
  "@modelcontextprotocol/server": "^2.1.0",
27
27
  "zod": "^4.6.2",
28
- "@sendora/sdk": "^4.4.0"
28
+ "@sendora/sdk": "^5.0.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@anthropic-ai/mcpb": "2.1.2",
@@ -39,11 +39,11 @@
39
39
  "fflate": "^0.8.3",
40
40
  "typescript": "~7.0.2",
41
41
  "vitest": "^4.1.11",
42
- "@sendora/db": "0.0.0",
43
42
  "@sendora/api": "0.0.0",
44
43
  "@sendora/observability": "0.0.0",
45
- "@sendora/domain": "0.0.0",
46
- "@sendora/site": "0.0.0"
44
+ "@sendora/site": "0.0.0",
45
+ "@sendora/db": "0.0.0",
46
+ "@sendora/domain": "0.0.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "pnpm --filter @sendora/sdk build && node scripts/build-docs.ts && node scripts/build-screens.ts && node -e \"fs.rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node -e \"fs.mkdirSync('dist/generated',{recursive:true});fs.copyFileSync('src/generated/docs.json','dist/generated/docs.json');fs.cpSync('src/generated/screens','dist/generated/screens',{recursive:true})\"",