@happykiller/sunny-mcp 0.1.0
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.
- package/dist/graphql/errors.d.ts +16 -0
- package/dist/graphql/errors.d.ts.map +1 -0
- package/dist/graphql/errors.js +39 -0
- package/dist/graphql/errors.js.map +1 -0
- package/dist/graphql/session.d.ts +34 -0
- package/dist/graphql/session.d.ts.map +1 -0
- package/dist/graphql/session.js +118 -0
- package/dist/graphql/session.js.map +1 -0
- package/dist/graphql/tokenCache.d.ts +13 -0
- package/dist/graphql/tokenCache.d.ts.map +1 -0
- package/dist/graphql/tokenCache.js +78 -0
- package/dist/graphql/tokenCache.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +6 -0
- package/dist/logger.js.map +1 -0
- package/dist/safety/target.d.ts +13 -0
- package/dist/safety/target.d.ts.map +1 -0
- package/dist/safety/target.js +34 -0
- package/dist/safety/target.js.map +1 -0
- package/dist/server/contract.d.ts +4 -0
- package/dist/server/contract.d.ts.map +1 -0
- package/dist/server/contract.js +10 -0
- package/dist/server/contract.js.map +1 -0
- package/dist/server/createMcpServer.d.ts +10 -0
- package/dist/server/createMcpServer.d.ts.map +1 -0
- package/dist/server/createMcpServer.js +58 -0
- package/dist/server/createMcpServer.js.map +1 -0
- package/dist/server/transport.d.ts +10 -0
- package/dist/server/transport.d.ts.map +1 -0
- package/dist/server/transport.js +39 -0
- package/dist/server/transport.js.map +1 -0
- package/dist/tools/defineTool.d.ts +28 -0
- package/dist/tools/defineTool.d.ts.map +1 -0
- package/dist/tools/defineTool.js +2 -0
- package/dist/tools/defineTool.js.map +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface GraphQLErreur {
|
|
2
|
+
message: string;
|
|
3
|
+
extensions?: {
|
|
4
|
+
code?: string;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export declare class GraphQLCallError extends Error {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly details: unknown;
|
|
10
|
+
constructor(code: string, details: unknown);
|
|
11
|
+
}
|
|
12
|
+
export declare function mapGraphQLErrors(erreurs: GraphQLErreur[]): GraphQLCallError;
|
|
13
|
+
/** Le serveur ne distingue pas toujours « jeton absent » de « jeton périmé » :
|
|
14
|
+
* les deux ressortent en UNAUTHENTICATED, parfois en FORBIDDEN. */
|
|
15
|
+
export declare function estErreurAuth(erreurs: GraphQLErreur[]): boolean;
|
|
16
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/graphql/errors.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAChC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAEvC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO;gBADhB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO;CAK5B;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,gBAAgB,CAU3E;AAED;oEACoE;AACpE,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,OAAO,CAS/D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/graphql/errors.ts
|
|
2
|
+
// Les usecases de sunny-apis lèvent des `Error` dont le message EST le code
|
|
3
|
+
// (`GET_USER_USECASE_USER_NOT_FOUND`…). GraphQL les rend en INTERNAL_SERVER_ERROR
|
|
4
|
+
// avec ce code en message. On les remonte tels quels : un agent doit pouvoir
|
|
5
|
+
// distinguer « ce compte n'existe pas » d'une panne, sans lire du français.
|
|
6
|
+
export class GraphQLCallError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
details;
|
|
9
|
+
constructor(code, details) {
|
|
10
|
+
super(code);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.details = details;
|
|
13
|
+
this.name = 'GraphQLCallError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Un message tout en MAJUSCULES_AVEC_UNDERSCORES est un code métier, pas une
|
|
17
|
+
* phrase destinée à un humain. */
|
|
18
|
+
const estCodeMetier = (m) => /^[A-Z][A-Z0-9_]{3,}$/.test(m.trim());
|
|
19
|
+
export function mapGraphQLErrors(erreurs) {
|
|
20
|
+
const premiere = erreurs[0];
|
|
21
|
+
const message = premiere?.message ?? 'erreur GraphQL sans message';
|
|
22
|
+
const code = estCodeMetier(message)
|
|
23
|
+
? message.trim()
|
|
24
|
+
: (premiere?.extensions?.code ?? 'GRAPHQL_ERROR');
|
|
25
|
+
return new GraphQLCallError(code, {
|
|
26
|
+
messages: erreurs.map((e) => e.message),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Le serveur ne distingue pas toujours « jeton absent » de « jeton périmé » :
|
|
30
|
+
* les deux ressortent en UNAUTHENTICATED, parfois en FORBIDDEN. */
|
|
31
|
+
export function estErreurAuth(erreurs) {
|
|
32
|
+
return erreurs.some((e) => {
|
|
33
|
+
const code = e.extensions?.code ?? '';
|
|
34
|
+
return (code === 'UNAUTHENTICATED' ||
|
|
35
|
+
code === 'FORBIDDEN' ||
|
|
36
|
+
/unauthorized|unauthenticated|jwt|token/i.test(e.message ?? ''));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/graphql/errors.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,4EAA4E;AAC5E,kFAAkF;AAClF,6EAA6E;AAC7E,4EAA4E;AAO5E,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAE9B;IACA;IAFX,YACW,IAAY,EACZ,OAAgB;QAEzB,KAAK,CAAC,IAAI,CAAC,CAAC;QAHH,SAAI,GAAJ,IAAI,CAAQ;QACZ,YAAO,GAAP,OAAO,CAAS;QAGzB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED;mCACmC;AACnC,MAAM,aAAa,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAE3E,MAAM,UAAU,gBAAgB,CAAC,OAAwB;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,QAAQ,EAAE,OAAO,IAAI,6BAA6B,CAAC;IACnE,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC;QACjC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE;QAChB,CAAC,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,IAAI,eAAe,CAAC,CAAC;IAEpD,OAAO,IAAI,gBAAgB,CAAC,IAAI,EAAE;QAChC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;KACxC,CAAC,CAAC;AACL,CAAC;AAED;oEACoE;AACpE,MAAM,UAAU,aAAa,CAAC,OAAwB;IACpD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QACxB,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC;QACtC,OAAO,CACL,IAAI,KAAK,iBAAiB;YAC1B,IAAI,KAAK,WAAW;YACpB,yCAAyC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAChE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TokenCache } from './tokenCache.js';
|
|
2
|
+
import type { Logger } from '../logger.js';
|
|
3
|
+
export interface SessionOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
/** Document d'authentification du produit. Doit accepter les variables
|
|
6
|
+
* `login` / `password` et rendre un jeton à `extraireToken`. */
|
|
7
|
+
authDocument: string;
|
|
8
|
+
extraireToken: (data: any) => string | undefined;
|
|
9
|
+
login?: string;
|
|
10
|
+
password?: string;
|
|
11
|
+
/** Jeton fixé d'avance : court-circuite toute connexion. */
|
|
12
|
+
token?: string;
|
|
13
|
+
cache?: TokenCache;
|
|
14
|
+
/** En-tête par lequel le serveur réémet un jeton frais à chaque requête
|
|
15
|
+
* authentifiée (`seguri-refresh-token` chez Siguri). L'adopter évite de
|
|
16
|
+
* repasser par `auth`, dont le quota est serré. */
|
|
17
|
+
refreshHeader?: string;
|
|
18
|
+
logger?: Logger;
|
|
19
|
+
fetchImpl?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
export declare class GraphQLSession {
|
|
22
|
+
private readonly opts;
|
|
23
|
+
private token?;
|
|
24
|
+
private readonly logger;
|
|
25
|
+
private readonly appel;
|
|
26
|
+
constructor(opts: SessionOptions);
|
|
27
|
+
request<T = any>(document: string, variables?: Record<string, unknown>): Promise<T>;
|
|
28
|
+
/** Jeton courant, sans forcer de connexion si l'on en a déjà un valide. */
|
|
29
|
+
jeton(): Promise<string>;
|
|
30
|
+
connexion(): Promise<string>;
|
|
31
|
+
private envoyer;
|
|
32
|
+
private adopterRafraichissement;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/graphql/session.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ;qEACiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;wDAEoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,qBAAa,cAAc;IAKb,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBAER,IAAI,EAAE,cAAc;IAM3C,OAAO,CAAC,CAAC,GAAG,GAAG,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACtC,OAAO,CAAC,CAAC,CAAC;IAoBb,2EAA2E;IACrE,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAYxB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;YA0BpB,OAAO;IA6CrB,OAAO,CAAC,uBAAuB;CAQhC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// src/graphql/session.ts
|
|
2
|
+
// Session GraphQL authentifiée, réutilisable par n'importe quel produit : le
|
|
3
|
+
// document d'authentification et le nom de l'en-tête de rafraîchissement sont
|
|
4
|
+
// des paramètres, pas des constantes.
|
|
5
|
+
import { GraphQLCallError, estErreurAuth, mapGraphQLErrors, } from './errors.js';
|
|
6
|
+
import { silentLogger } from '../logger.js';
|
|
7
|
+
export class GraphQLSession {
|
|
8
|
+
opts;
|
|
9
|
+
token;
|
|
10
|
+
logger;
|
|
11
|
+
appel;
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.opts = opts;
|
|
14
|
+
this.logger = opts.logger ?? silentLogger;
|
|
15
|
+
this.appel = opts.fetchImpl ?? fetch;
|
|
16
|
+
this.token = opts.token;
|
|
17
|
+
}
|
|
18
|
+
async request(document, variables = {}) {
|
|
19
|
+
const token = await this.jeton();
|
|
20
|
+
const premiere = await this.envoyer(document, variables, token);
|
|
21
|
+
if (premiere.erreurs && estErreurAuth(premiere.erreurs)) {
|
|
22
|
+
// Le jeton a été refusé côté serveur : révoqué, compte désactivé, ou
|
|
23
|
+
// secret tourné. Une seule reprise — jamais de boucle.
|
|
24
|
+
this.logger.info('jeton refusé, reconnexion');
|
|
25
|
+
this.token = undefined;
|
|
26
|
+
this.opts.cache?.vider();
|
|
27
|
+
const frais = await this.connexion();
|
|
28
|
+
const seconde = await this.envoyer(document, variables, frais);
|
|
29
|
+
if (seconde.erreurs)
|
|
30
|
+
throw mapGraphQLErrors(seconde.erreurs);
|
|
31
|
+
return seconde.data;
|
|
32
|
+
}
|
|
33
|
+
if (premiere.erreurs)
|
|
34
|
+
throw mapGraphQLErrors(premiere.erreurs);
|
|
35
|
+
return premiere.data;
|
|
36
|
+
}
|
|
37
|
+
/** Jeton courant, sans forcer de connexion si l'on en a déjà un valide. */
|
|
38
|
+
async jeton() {
|
|
39
|
+
if (this.opts.token)
|
|
40
|
+
return this.opts.token;
|
|
41
|
+
if (this.token)
|
|
42
|
+
return this.token;
|
|
43
|
+
const encache = this.opts.cache?.lire();
|
|
44
|
+
if (encache) {
|
|
45
|
+
this.token = encache;
|
|
46
|
+
return encache;
|
|
47
|
+
}
|
|
48
|
+
return this.connexion();
|
|
49
|
+
}
|
|
50
|
+
async connexion() {
|
|
51
|
+
if (!this.opts.login || !this.opts.password) {
|
|
52
|
+
throw new GraphQLCallError('MISSING_CREDENTIALS', {
|
|
53
|
+
hint: "Ni jeton fixé ni couple login/mot de passe n'a été fourni.",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const { data, erreurs } = await this.envoyer(this.opts.authDocument, {
|
|
57
|
+
login: this.opts.login,
|
|
58
|
+
password: this.opts.password,
|
|
59
|
+
});
|
|
60
|
+
if (erreurs)
|
|
61
|
+
throw mapGraphQLErrors(erreurs);
|
|
62
|
+
const token = this.opts.extraireToken(data);
|
|
63
|
+
if (!token) {
|
|
64
|
+
throw new GraphQLCallError('AUTH_NO_TOKEN', {
|
|
65
|
+
hint: "L'authentification a répondu sans jeton.",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
this.token = token;
|
|
69
|
+
this.opts.cache?.ecrire(token);
|
|
70
|
+
return token;
|
|
71
|
+
}
|
|
72
|
+
async envoyer(document, variables, token) {
|
|
73
|
+
const reponse = await this.appel(this.opts.url, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'content-type': 'application/json',
|
|
77
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({ query: document, variables }),
|
|
80
|
+
});
|
|
81
|
+
// Le quota d'authentification est serré (10/min chez Siguri) : mieux vaut le
|
|
82
|
+
// dire franchement que laisser l'agent réessayer en aveugle.
|
|
83
|
+
if (reponse.status === 429) {
|
|
84
|
+
throw new GraphQLCallError('TOO_MANY_REQUESTS', {
|
|
85
|
+
retryAfter: reponse.headers.get('retry-after'),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
this.adopterRafraichissement(reponse);
|
|
89
|
+
const texte = await reponse.text();
|
|
90
|
+
if (!texte) {
|
|
91
|
+
throw new GraphQLCallError('EMPTY_RESPONSE', { status: reponse.status });
|
|
92
|
+
}
|
|
93
|
+
let corps;
|
|
94
|
+
try {
|
|
95
|
+
corps = JSON.parse(texte);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new GraphQLCallError('INVALID_RESPONSE', {
|
|
99
|
+
status: reponse.status,
|
|
100
|
+
body: texte.slice(0, 400),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
data: corps.data,
|
|
105
|
+
erreurs: corps.errors?.length ? corps.errors : undefined,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
adopterRafraichissement(reponse) {
|
|
109
|
+
if (!this.opts.refreshHeader)
|
|
110
|
+
return;
|
|
111
|
+
const frais = reponse.headers.get(this.opts.refreshHeader);
|
|
112
|
+
if (frais && frais !== this.token) {
|
|
113
|
+
this.token = frais;
|
|
114
|
+
this.opts.cache?.ecrire(frais);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/graphql/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function jwtExpiration(token: string): number | null;
|
|
2
|
+
export declare function tokenEncoreValide(token: string, maintenant?: number): boolean;
|
|
3
|
+
/** Le cache vit hors du dépôt : un `.token` déposé dans un dossier de projet
|
|
4
|
+
* finit par être copié, partagé ou committé. */
|
|
5
|
+
export declare function cheminCache(produit: string, cle: string): string;
|
|
6
|
+
export declare class TokenCache {
|
|
7
|
+
private readonly chemin;
|
|
8
|
+
constructor(chemin: string);
|
|
9
|
+
lire(): string | null;
|
|
10
|
+
ecrire(token: string): void;
|
|
11
|
+
vider(): void;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=tokenCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenCache.d.ts","sourceRoot":"","sources":["../../src/graphql/tokenCache.ts"],"names":[],"mappings":"AAsBA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW1D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,SAAa,GAAG,OAAO,CAMjF;AAED;iDACiD;AACjD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE3C,IAAI,IAAI,MAAM,GAAG,IAAI;IAWrB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAU3B,KAAK,IAAI,IAAI;CAOd"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/graphql/tokenCache.ts
|
|
2
|
+
// Cache de jeton sur disque, hors du dépôt. Reprend la logique éprouvée de
|
|
3
|
+
// `siguri-admin/scripts/sgql.sh` : on décode `exp` sans vérifier la signature —
|
|
4
|
+
// le client n'a pas le secret et n'en a pas besoin, la vérification est
|
|
5
|
+
// l'affaire du serveur. Le seul but est d'éviter de rejouer `auth`, bridé à
|
|
6
|
+
// 10 requêtes par minute.
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { homedir, tmpdir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
11
|
+
/** Marge avant expiration : un jeton qui expire pendant le vol de la requête
|
|
12
|
+
* coûte un aller-retour inutile et une erreur trompeuse. */
|
|
13
|
+
const MARGE_SECONDES = 60;
|
|
14
|
+
export function jwtExpiration(token) {
|
|
15
|
+
const parts = token.split('.');
|
|
16
|
+
if (parts.length !== 3)
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const charge = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
20
|
+
return typeof charge?.exp === 'number' ? charge.exp : null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function tokenEncoreValide(token, maintenant = Date.now()) {
|
|
27
|
+
const exp = jwtExpiration(token);
|
|
28
|
+
// Un jeton sans `exp` est indatable : on préfère se reconnecter plutôt que de
|
|
29
|
+
// parier sur sa validité.
|
|
30
|
+
if (exp === null)
|
|
31
|
+
return false;
|
|
32
|
+
return exp * 1000 > maintenant + MARGE_SECONDES * 1000;
|
|
33
|
+
}
|
|
34
|
+
/** Le cache vit hors du dépôt : un `.token` déposé dans un dossier de projet
|
|
35
|
+
* finit par être copié, partagé ou committé. */
|
|
36
|
+
export function cheminCache(produit, cle) {
|
|
37
|
+
const base = process.env.XDG_CACHE_HOME ??
|
|
38
|
+
(homedir() ? join(homedir(), '.cache') : tmpdir());
|
|
39
|
+
const empreinte = createHash('sha256').update(cle).digest('hex').slice(0, 32);
|
|
40
|
+
return join(base, 'sunny-mcp', `${produit}-${empreinte}.token`);
|
|
41
|
+
}
|
|
42
|
+
export class TokenCache {
|
|
43
|
+
chemin;
|
|
44
|
+
constructor(chemin) {
|
|
45
|
+
this.chemin = chemin;
|
|
46
|
+
}
|
|
47
|
+
lire() {
|
|
48
|
+
try {
|
|
49
|
+
if (!existsSync(this.chemin))
|
|
50
|
+
return null;
|
|
51
|
+
const token = readFileSync(this.chemin, 'utf8').trim();
|
|
52
|
+
return token && tokenEncoreValide(token) ? token : null;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Un cache illisible n'est pas une panne : on se reconnecte.
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
ecrire(token) {
|
|
60
|
+
try {
|
|
61
|
+
mkdirSync(join(this.chemin, '..'), { recursive: true, mode: 0o700 });
|
|
62
|
+
writeFileSync(this.chemin, token, { encoding: 'utf8', mode: 0o600 });
|
|
63
|
+
chmodSync(this.chemin, 0o600);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Un cache non écrit dégrade la performance, pas la correction.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
vider() {
|
|
70
|
+
try {
|
|
71
|
+
rmSync(this.chemin, { force: true });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* idem */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=tokenCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenCache.js","sourceRoot":"","sources":["../../src/graphql/tokenCache.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { ok, fail } from './server/contract.js';
|
|
2
|
+
export { createMcpServer } from './server/createMcpServer.js';
|
|
3
|
+
export type { CreateServerOptions } from './server/createMcpServer.js';
|
|
4
|
+
export { startTransport } from './server/transport.js';
|
|
5
|
+
export type { StartOptions, TransportKind } from './server/transport.js';
|
|
6
|
+
export { defineTool } from './tools/defineTool.js';
|
|
7
|
+
export type { ToolCatalog, ToolContext, ToolDefinition, } from './tools/defineTool.js';
|
|
8
|
+
export { GraphQLSession } from './graphql/session.js';
|
|
9
|
+
export type { SessionOptions } from './graphql/session.js';
|
|
10
|
+
export { GraphQLCallError, estErreurAuth, mapGraphQLErrors, } from './graphql/errors.js';
|
|
11
|
+
export type { GraphQLErreur } from './graphql/errors.js';
|
|
12
|
+
export { TokenCache, cheminCache, jwtExpiration, tokenEncoreValide, } from './graphql/tokenCache.js';
|
|
13
|
+
export { AVEU_PROD, TargetRefusedError, resolveTarget, } from './safety/target.js';
|
|
14
|
+
export type { Target } from './safety/target.js';
|
|
15
|
+
export { silentLogger, stderrLogger } from './logger.js';
|
|
16
|
+
export type { Logger } from './logger.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEzE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,YAAY,EACV,WAAW,EACX,WAAW,EACX,cAAc,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACL,UAAU,EACV,WAAW,EACX,aAAa,EACb,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,aAAa,GACd,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzD,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/index.ts — surface publique du socle.
|
|
2
|
+
export { ok, fail } from './server/contract.js';
|
|
3
|
+
export { createMcpServer } from './server/createMcpServer.js';
|
|
4
|
+
export { startTransport } from './server/transport.js';
|
|
5
|
+
export { defineTool } from './tools/defineTool.js';
|
|
6
|
+
export { GraphQLSession } from './graphql/session.js';
|
|
7
|
+
export { GraphQLCallError, estErreurAuth, mapGraphQLErrors, } from './graphql/errors.js';
|
|
8
|
+
export { TokenCache, cheminCache, jwtExpiration, tokenEncoreValide, } from './graphql/tokenCache.js';
|
|
9
|
+
export { AVEU_PROD, TargetRefusedError, resolveTarget, } from './safety/target.js';
|
|
10
|
+
export { silentLogger, stderrLogger } from './logger.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAOnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,UAAU,EACV,WAAW,EACX,aAAa,EACb,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,aAAa,GACd,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,MAG7C,CAAC;AAEH,eAAO,MAAM,YAAY,EAAE,MAA4C,CAAC"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const stderrLogger = (prefixe) => ({
|
|
2
|
+
info: (m) => process.stderr.write(`[${prefixe}] ${m}\n`),
|
|
3
|
+
error: (m) => process.stderr.write(`[${prefixe}] ERREUR ${m}\n`),
|
|
4
|
+
});
|
|
5
|
+
export const silentLogger = { info: () => { }, error: () => { } };
|
|
6
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,CAAC;IACxD,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,CAAC;IACxD,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,OAAO,YAAY,CAAC,IAAI,CAAC;CACjE,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAW,EAAE,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface Target {
|
|
2
|
+
url: string;
|
|
3
|
+
host: string;
|
|
4
|
+
isProd: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** Le mot de passe est volontairement long et non devinable : on ne le pose pas
|
|
7
|
+
* par distraction, contrairement à `true` ou `1`. */
|
|
8
|
+
export declare const AVEU_PROD = "yes-i-know";
|
|
9
|
+
export declare class TargetRefusedError extends Error {
|
|
10
|
+
readonly code = "TARGET_REFUSED";
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveTarget(rawUrl: string, aveu?: string): Target;
|
|
13
|
+
//# sourceMappingURL=target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../../src/safety/target.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;sDACsD;AACtD,eAAO,MAAM,SAAS,eAAe,CAAC;AAEtC,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,oBAAoB;CAClC;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAqBnE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/safety/target.ts
|
|
2
|
+
// Garde-fou de cible : transposition du cran d'arrêt de `bin/dev-mongo.sh`, qui
|
|
3
|
+
// refuse d'écrire sans `--write`. Il ne protège pas contre un attaquant — il
|
|
4
|
+
// protège contre soi-même, un soir où l'on croit viser le local.
|
|
5
|
+
const HOTES_LOCAUX = new Set([
|
|
6
|
+
'localhost',
|
|
7
|
+
'127.0.0.1',
|
|
8
|
+
'::1',
|
|
9
|
+
'[::1]',
|
|
10
|
+
'0.0.0.0',
|
|
11
|
+
]);
|
|
12
|
+
/** Le mot de passe est volontairement long et non devinable : on ne le pose pas
|
|
13
|
+
* par distraction, contrairement à `true` ou `1`. */
|
|
14
|
+
export const AVEU_PROD = 'yes-i-know';
|
|
15
|
+
export class TargetRefusedError extends Error {
|
|
16
|
+
code = 'TARGET_REFUSED';
|
|
17
|
+
}
|
|
18
|
+
export function resolveTarget(rawUrl, aveu) {
|
|
19
|
+
let url;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(rawUrl);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new TargetRefusedError(`URL d'API invalide : ${JSON.stringify(rawUrl)}`);
|
|
25
|
+
}
|
|
26
|
+
const host = url.hostname;
|
|
27
|
+
const isProd = !HOTES_LOCAUX.has(host);
|
|
28
|
+
if (isProd && aveu !== AVEU_PROD) {
|
|
29
|
+
throw new TargetRefusedError(`Cible non locale (${host}). Pour l'autoriser délibérément, poser ` +
|
|
30
|
+
`SUNNY_MCP_ALLOW_PROD=${AVEU_PROD}. Toute autre valeur est refusée.`);
|
|
31
|
+
}
|
|
32
|
+
return { url: url.toString(), host, isProd };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target.js","sourceRoot":"","sources":["../../src/safety/target.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,gFAAgF;AAChF,6EAA6E;AAC7E,iEAAiE;AAEjE,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;IAC3B,WAAW;IACX,WAAW;IACX,KAAK;IACL,OAAO;IACP,SAAS;CACV,CAAC,CAAC;AAQH;sDACsD;AACtD,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC;AAEtC,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,GAAG,gBAAgB,CAAC;CAClC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,IAAa;IACzD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,kBAAkB,CAC1B,wBAAwB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CACjD,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC;IAC1B,MAAM,MAAM,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEvC,IAAI,MAAM,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,kBAAkB,CAC1B,qBAAqB,IAAI,0CAA0C;YACjE,wBAAwB,SAAS,mCAAmC,CACvE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/server/contract.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,eAAO,MAAM,EAAE,GAAI,QAAQ,OAAO,KAAG,cAEnC,CAAC;AAEH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,EAAE,UAAU,OAAO,KAAG,cAKrD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const ok = (result) => ({
|
|
2
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
3
|
+
});
|
|
4
|
+
export const fail = (code, details) => ({
|
|
5
|
+
isError: true,
|
|
6
|
+
content: [
|
|
7
|
+
{ type: 'text', text: JSON.stringify({ error: code, details }, null, 2) },
|
|
8
|
+
],
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/server/contract.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAe,EAAkB,EAAE,CAAC,CAAC;IACtD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;CACnE,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,OAAiB,EAAkB,EAAE,CAAC,CAAC;IACxE,OAAO,EAAE,IAAI;IACb,OAAO,EAAE;QACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;KAC1E;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ToolCatalog, ToolContext } from '../tools/defineTool.js';
|
|
3
|
+
export interface CreateServerOptions {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
catalog: ToolCatalog;
|
|
7
|
+
ctx: ToolContext;
|
|
8
|
+
}
|
|
9
|
+
export declare function createMcpServer(opts: CreateServerOptions): McpServer;
|
|
10
|
+
//# sourceMappingURL=createMcpServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createMcpServer.d.ts","sourceRoot":"","sources":["../../src/server/createMcpServer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAEvE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,WAAW,CAAC;IACrB,GAAG,EAAE,WAAW,CAAC;CAClB;AAMD,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,SAAS,CA0DpE"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/server/createMcpServer.ts
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { ok, fail } from './contract.js';
|
|
4
|
+
import { GraphQLCallError } from '../graphql/errors.js';
|
|
5
|
+
/** Préfixe apposé en clair sur les outils écrivant en production : la seule
|
|
6
|
+
* chose que l'agent lit avant d'agir, c'est la description. */
|
|
7
|
+
const PREFIXE_PROD = '[PRODUCTION] ';
|
|
8
|
+
export function createMcpServer(opts) {
|
|
9
|
+
const { catalog, ctx } = opts;
|
|
10
|
+
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
11
|
+
const noms = new Set();
|
|
12
|
+
for (const tool of catalog) {
|
|
13
|
+
if (noms.has(tool.name)) {
|
|
14
|
+
throw new Error(`Outil déclaré deux fois : ${tool.name}`);
|
|
15
|
+
}
|
|
16
|
+
noms.add(tool.name);
|
|
17
|
+
const description = ctx.target.isProd && tool.requiresWrite
|
|
18
|
+
? PREFIXE_PROD + tool.description
|
|
19
|
+
: tool.description;
|
|
20
|
+
server.registerTool(tool.name, {
|
|
21
|
+
title: tool.title,
|
|
22
|
+
description,
|
|
23
|
+
inputSchema: tool.inputSchema,
|
|
24
|
+
annotations: tool.annotations,
|
|
25
|
+
}, async (args) => {
|
|
26
|
+
if (tool.requiresWrite && !ctx.allowWrites) {
|
|
27
|
+
// Refus AVANT toute requête : rien ne part sur le réseau.
|
|
28
|
+
return fail('WRITES_DISABLED', {
|
|
29
|
+
tool: tool.name,
|
|
30
|
+
target: ctx.target.url,
|
|
31
|
+
hint: 'Poser SUNNY_MCP_ALLOW_WRITES=true pour autoriser les écritures.',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const resultat = await tool.execute(args, ctx);
|
|
36
|
+
// Chaque réponse rappelle où elle a agi : l'agent et le lecteur de la
|
|
37
|
+
// transcription voient la cible sans avoir à la deviner.
|
|
38
|
+
return ok({
|
|
39
|
+
...resultat,
|
|
40
|
+
target: ctx.target.url,
|
|
41
|
+
is_prod: ctx.target.isProd,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
ctx.logger.error(`${tool.name} : ${e?.message ?? e}`);
|
|
46
|
+
if (e instanceof GraphQLCallError) {
|
|
47
|
+
return fail(e.code, { ...e.details, tool: tool.name });
|
|
48
|
+
}
|
|
49
|
+
return fail(e?.code ?? 'TOOL_FAILED', {
|
|
50
|
+
message: e?.message ?? String(e),
|
|
51
|
+
tool: tool.name,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return server;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=createMcpServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createMcpServer.js","sourceRoot":"","sources":["../../src/server/createMcpServer.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAUxD;gEACgE;AAChE,MAAM,YAAY,GAAG,eAAe,CAAC;AAErC,MAAM,UAAU,eAAe,CAAC,IAAyB;IACvD,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAEzE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEpB,MAAM,WAAW,GACf,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,aAAa;YACrC,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,WAAW;YACjC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;QAEvB,MAAM,CAAC,YAAY,CACjB,IAAI,CAAC,IAAI,EACT;YACE,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW;YACX,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,EACD,KAAK,EAAE,IAAS,EAAE,EAAE;YAClB,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBAC3C,0DAA0D;gBAC1D,OAAO,IAAI,CAAC,iBAAiB,EAAE;oBAC7B,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG;oBACtB,IAAI,EAAE,iEAAiE;iBACxE,CAAC,CAAC;YACL,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC/C,sEAAsE;gBACtE,yDAAyD;gBACzD,OAAO,EAAE,CAAC;oBACR,GAAI,QAAmB;oBACvB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG;oBACtB,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM;iBAC3B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBAChB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,MAAM,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC;gBACtD,IAAI,CAAC,YAAY,gBAAgB,EAAE,CAAC;oBAClC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,GAAI,CAAC,CAAC,OAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBACrE,CAAC;gBACD,OAAO,IAAI,CAAC,CAAC,EAAE,IAAI,IAAI,aAAa,EAAE;oBACpC,OAAO,EAAE,CAAC,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC;oBAChC,IAAI,EAAE,IAAI,CAAC,IAAI;iBAChB,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { Logger } from '../logger.js';
|
|
3
|
+
export type TransportKind = 'stdio' | 'http';
|
|
4
|
+
export interface StartOptions {
|
|
5
|
+
transport?: TransportKind;
|
|
6
|
+
port?: number;
|
|
7
|
+
logger?: Logger;
|
|
8
|
+
}
|
|
9
|
+
export declare function startTransport(server: McpServer, opts?: StartOptions): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,IAAI,CAAC,CAkCf"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/server/transport.ts
|
|
2
|
+
// Deux transports pour un même serveur, choisis par variable d'environnement :
|
|
3
|
+
// `stdio` pour un client comme Claude Code, `http` pour le débogage manuel et un
|
|
4
|
+
// éventuel déploiement multi-client. Gabarit repris de kalifa.
|
|
5
|
+
import express from 'express';
|
|
6
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
8
|
+
import { silentLogger } from '../logger.js';
|
|
9
|
+
export async function startTransport(server, opts = {}) {
|
|
10
|
+
const logger = opts.logger ?? silentLogger;
|
|
11
|
+
const kind = opts.transport ?? 'stdio';
|
|
12
|
+
if (kind === 'stdio') {
|
|
13
|
+
await server.connect(new StdioServerTransport());
|
|
14
|
+
logger.info('transport stdio prêt');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const app = express();
|
|
18
|
+
app.use(express.json({ limit: '4mb' }));
|
|
19
|
+
// `sessionIdGenerator: undefined` = mode sans état : aucune session à tenir
|
|
20
|
+
// côté serveur, donc rien à répliquer si l'on déploie plusieurs instances.
|
|
21
|
+
const transport = new StreamableHTTPServerTransport({
|
|
22
|
+
sessionIdGenerator: undefined,
|
|
23
|
+
});
|
|
24
|
+
await server.connect(transport);
|
|
25
|
+
const relais = async (req, res) => {
|
|
26
|
+
await transport.handleRequest(req, res, req.body);
|
|
27
|
+
};
|
|
28
|
+
app.post('/mcp', relais);
|
|
29
|
+
app.get('/mcp', relais);
|
|
30
|
+
app.delete('/mcp', relais);
|
|
31
|
+
const port = opts.port ?? 3000;
|
|
32
|
+
await new Promise((resolve) => {
|
|
33
|
+
app.listen(port, () => {
|
|
34
|
+
logger.info(`transport http prêt sur :${port}/mcp`);
|
|
35
|
+
resolve();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,+EAA+E;AAC/E,iFAAiF;AACjF,+DAA+D;AAC/D,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAInG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAU5C,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAiB,EACjB,OAAqB,EAAE;IAEvB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,YAAY,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC;IAEvC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACpC,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAExC,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC;QAClD,kBAAkB,EAAE,SAAS;KAC9B,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,MAAM,MAAM,GAAG,KAAK,EAAE,GAAoB,EAAE,GAAqB,EAAE,EAAE;QACnE,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC,CAAC;IACF,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxB,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE3B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;IAC/B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;YACpB,MAAM,CAAC,IAAI,CAAC,4BAA4B,IAAI,MAAM,CAAC,CAAC;YACpD,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import type { Logger } from '../logger.js';
|
|
4
|
+
import type { Target } from '../safety/target.js';
|
|
5
|
+
import type { GraphQLSession } from '../graphql/session.js';
|
|
6
|
+
export interface ToolContext {
|
|
7
|
+
gql: GraphQLSession;
|
|
8
|
+
target: Target;
|
|
9
|
+
/** Interrupteur d'écriture, indépendant de la cible : il faut deux erreurs
|
|
10
|
+
* pour écrire en production, pas une. */
|
|
11
|
+
allowWrites: boolean;
|
|
12
|
+
logger: Logger;
|
|
13
|
+
}
|
|
14
|
+
export interface ToolDefinition<S extends z.ZodRawShape = z.ZodRawShape> {
|
|
15
|
+
name: string;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
inputSchema: S;
|
|
19
|
+
annotations?: ToolAnnotations;
|
|
20
|
+
/** Marque l'outil comme écrivant. Le socle le refuse tant que `allowWrites`
|
|
21
|
+
* est faux, sans même émettre de requête. */
|
|
22
|
+
requiresWrite?: boolean;
|
|
23
|
+
/** Rend l'objet métier ; le socle l'enveloppe dans le contrat de réponse. */
|
|
24
|
+
execute(args: z.infer<z.ZodObject<S>>, ctx: ToolContext): Promise<unknown>;
|
|
25
|
+
}
|
|
26
|
+
export declare const defineTool: <S extends z.ZodRawShape>(d: ToolDefinition<S>) => ToolDefinition<S>;
|
|
27
|
+
export type ToolCatalog = ReadonlyArray<ToolDefinition<any>>;
|
|
28
|
+
//# sourceMappingURL=defineTool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defineTool.d.ts","sourceRoot":"","sources":["../../src/tools/defineTool.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAE1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf;8CAC0C;IAC1C,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC;IACf,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;kDAC8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,6EAA6E;IAC7E,OAAO,CACL,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC7B,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,sBAAM,CAAC;AAE/E,MAAM,MAAM,WAAW,GAAG,aAAa,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defineTool.js","sourceRoot":"","sources":["../../src/tools/defineTool.ts"],"names":[],"mappings":"AAqCA,MAAM,CAAC,MAAM,UAAU,GAAG,CAA0B,CAAoB,EAAE,EAAE,CAAC,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happykiller/sunny-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Socle MCP partagé : transport, session GraphQL, registre d'outils et garde-fous, pour exposer le back-office d'un produit aux agents.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": ["dist"],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
18
|
+
"test": "tsx --test $(find src -name '*.test.ts' | sort)",
|
|
19
|
+
"check": "tsc --noEmit -p tsconfig.json && npm test"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
23
|
+
"express": "^5.2.1",
|
|
24
|
+
"zod": "^4.3.6"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/express": "^5.0.6",
|
|
28
|
+
"@types/node": "^22.15.0",
|
|
29
|
+
"tsx": "^4.21.0",
|
|
30
|
+
"typescript": "^5.9.0"
|
|
31
|
+
}
|
|
32
|
+
}
|